- frank-dragoon: measured the response_format contract. json_schema and json_object answer
schema-valid JSON at temperature 0 and draw a 400 at any sampled temperature, and because
the sampler default is server-side an absent temperature is sampled. The page claimed
"response_format is unsupported", which sends the next agent past a working feature.
- frank-dragoon: new "The NPU lane" section. The pinned 0.16.2 image already ships the
five-program AIE catalog including qwen3-embedding-0.6b, so /v1/embeddings is one env var
plus the accel device, the distro-XRT mount and the fabric-clock hold; embeddinggemma-2
needs 0.18.3+, the stock-kernel-driver memory-budget fix 0.18.8+. Host census recorded:
no /opt/xilinx/xrt, distro XRT 1:2.21.75 with libxrt_driver_xdna, clocks unheld.
- Open Notebook: scriptwriter probe verdict — greedy-only, and open-notebook's model record
is {credential, name, provider, type} with no per-row config, so it cannot ask for greedy;
frank-archon stays the writer. The embedding gap now has a named door instead of "build a
new llama.cpp slot".
- Frank TTS: "the NPU is not a door at all" was too broad. It is not a door for the TTS
family; halogen's precompiled catalog is a door on the same device.
- System Specs: NPU runtime row corrected to the distro XRT actually installed, and the
stale "frank-zealot serves 27B" line retired in favour of [[frank-zealot]] as sole owner.
Tests: ./tests/run.sh, 455 passed.
The page described an undeployed service whose scriptwriter was going to be
frank-zealot. Both halves are now false, and the reason is worth more than the
correction.
- Status, ports, coordinates: deployed 2026-10-19, guarded vhost, API published
on loopback only. The first episode is the proof: 747 s of speech in 301 s of
wall clock (2.5x real time, knobs at 1), download route 200 with the bearer
and 401 without.
- New section for the guard chain: provider-before-route, one upstream because
the image rewrites /api/* itself, and the API_URL law — autodetect appends
:5055, which is seerr's port on this host, so an unset API_URL yields a UI
that loads and an app that cannot call itself.
- Provisioned records table (credentials, model rows, dank-duo, dank-daily) and
the two contract traps measured on the live API: generate takes profile
NAMES, and the fleet has no /v1/embeddings, so every note save fails its
embed_note command while renders from content work fine.
- Why frank-archon writes the scripts: frank-zealot is reachable from this
netns only at 169.254.1.2, and open-notebook's URL guard refuses to store a
link-local base URL with no override. Census lands on [[Container Network
Namespaces]], which also gains the root-vs-rootless publish rule — a
root-published port is invisible to a rootless container, so "publish the
port" was the wrong remedy as written there.
- Frank TTS: the request's model field is ignored; only voice selects a pack.
Tests: ./tests/run.sh, 455 passed.
The cutover PR merged the runtime; this carries the surface that has to be
true afterwards.
- Engine page: the shape is now `--device Vulkan0` on the gfx1151 as a
co-tenant beside frank-dragoon; the registry/thread/streaming laws gain a
fourth — the device law — whose numbers stay in the verdict report, and the
deploy record gets the cutover steps including the `glslc` configure gate.
The CPU-era arm/rollback prose keeps the qualitative argument and drops the
dead numbers per the purpose test.
- Endpoint page: "Where the engine computes" now says iGPU + co-tenancy, and
states the NPU verdict in one paragraph instead of a research tour.
- Quadlet comment: it claimed supplementary groups are what grant render
access. On this host the udev rule world-writes the render nodes and the
container's `id` shows no groups at all, so keep-groups is belt-and-braces;
the RSS note now matches the device shape instead of the CPU trial.
- Verdict report: gate 2 (contention) recorded as measured after the cutover,
and the build-stage prediction corrected — the package is `glslc`, not
`libvulkan-dev glslang-dev`.
- index.md regenerated (one description changed).
The overnight spike settled whether frank-tts can run on the Strix Halo NPU.
It cannot, and the wall is the loader, not the silicon: AMD's own Linux
runtime opens /dev/accel/accel0, succeeds at the GEM ioctls, then declines
the NuTTS-Air graph without emitting a single log line — and the one run that
did load has never reproduced. The accelerator that does exist is the gfx1151
iGPU behind -DGGML_VULKAN=ON, a flag our own Containerfile.engine writes OFF,
and Frank set the target shape to co-tenancy: the engine resident beside
frank-dragoon, not a handover.
- report/tts-npu-spike-verdict-2026-10-19.md owns the measured result: the
exoneration chain, the two arms' real-time bars (12.5 codes/s deployed vs
50.00 for NuTTS-Air), the 65,536-entry speech-token census (ids
151,671-217,206), the host gates (rootless cannot read accel0; dpkg-deb the
vendor XRT, never dpkg -i), the iGPU census (18,523 of 116,733 MiB GTT in
use, 17 GiB host RAM available, GTT pages are host pages), the two gates
that decide co-tenancy (RADV heap for an APU tenant; dragoon's TTFT with a
synthesis in flight), and the five-file change surface for the trial.
- report/npu-tts-runtime-landscape-2026-10-19.md and
report/qwen3-tts-payload-portability-2026-10-19.md are the research slices;
both now point at the verdict page instead of ending in loose ends.
- The vault page gains "Where the engine computes", so an operator doing a TTS
task learns the NPU is a closed door without re-walking it.
No fleet change: frank-tts / frank-tts-engine still serve narrator1-3 on CPU.
tests: ./tests/run.sh 450/450 green.
The vault was 155 pages / 31,210 lines, most of it measurement ledgers for
payloads that no longer run. A throughput figure for a runtime nobody operates is
not a record: AI moves faster than the vault, so the number is wrong before
anyone reads it, and a wrong number in a runbook gets believed. Per the new
purpose test in `AGENTS.md` → *Documentation Policy*, it is destroyed.
Gone (`.AGENTTRASH/20261019-destruction/`, KILL-LIST.txt is the manifest):
- the `frank-medium` endpoint and its five era pages, `frank-npu`, FastFlowLM,
the ikllama/kingjones/dealign/frank-redo eras, the ling/swift15/freetoken/ubatch
campaign pages, qwen3.8 spec-matrix and strix-optimizations, the two
"Running Qwen3.6 …" pages;
- the whole `Pipeline Parallelism/` folder (no live script passes a pipeline flag)
and the multi-device/x4-pair/hybrid/offload experiment pages;
- every vLLM page — no vLLM endpoint exists on this fleet;
- `LLM/deployment.md` (`Architecture/Deployment Flow.md` is the test-referenced
owner) and the external surveys (SGLang, Lemonade, llama.cpp forks/diff,
self-hosted app census, the TTS survey) that were obsolete by construction;
- `log.md`, 2,380 lines. Git holds the chronology; a causal law belongs to the
page that owns the thing it governs, never to a diary.
Survivors: 101 pages, 17,542 lines, 0 OKF violations, 0 unresolved links. Eras
tables keep the window, payload, runtime, reason and rollback pointer; the rate
columns are blanked. The live shapes stay verbatim — those numbers are what the
deployer and `models.yml` consume.
Structural repair:
- 13 pages were the same document twice (each exactly 2x its HEAD size),
a second frontmatter block whose closing fence had been glued onto a body
line by a frontmatter rewrite that re-read its own output. Un-doubled by
cutting at the second frontmatter and keeping the copy whose body is a
superset; the orphan census reports 0 lost tokens against HEAD.
- log.md: one entry per date, bare `## YYYY-MM-DD` headings, newest first —
66 dated headings merged into 34, titles demoted to bold leads, every
content line verified present. The file had been descending at the head
and ascending at the tail.
Ownership moves:
- New `LLM/frank-medium.md` is the locator for the retired Dankserver
endpoint: era windows quoted from each era page's own banner, no numbers
restated, and the two unresolved date conflicts recorded instead of
silently resolved. `frank-archon.md` links to it instead of restating the
pre-rename window.
- The four Pipeline-Parallelism stubs (frontmatter only, no body) are gone;
`Multi-Node Runtimes.md` owns runtime construction, launch, mixed-vendor
verdicts and cross-split spec decode, and the four links that pointed at
the stubs were repointed at it or at the drafter page.
- Authentik's three stub pages fold into `Authentik.md`, with the process
detail and the outpost reference split out so the parent stays under the
length hint.
Verified corrections (host census + live probe, 2026-10-19):
- The MXFP4 checkpoint two pages called the "on-disk rollback" is gone:
/home/dank/models/ holds StillDeadcode, rad and tpl.jinja. Stated once, on
the era page that owns that payload; the swap page now links to it.
- frank-medium-radiance-mxfp4: the 4.24/7 accept rate belongs to the DFlash2
arm measured on the FP8 target, not to the MXFP4 era; the 3-family soak run
census is kept once (155 requests) instead of twice (~120 and 155).
- Fleet shapes re-confirmed against the live endpoints before touching any
page: 3x200000 fp8 KV, 2x200704, 2x262144.
Index rows are regenerated, not hand-carved. Net -1,849 vault lines.
18 pages cut their per-endpoint shape, payload, port and window claims and
link [[frank-archon]] / [[frank-dragoon]] / [[frank-zealot]] instead. Several
of the cut claims had already drifted -- the stack page still described the
kingjones ROCmFP4 era on ROCmFPX, OpenWebUI carried the 27B/3-slot bench
narrative, Hermes named a retired max_model_len and the old service name, and
the OMP harness page's live-window block had drifted off frank-zealot's real
262,144 (it is now labelled a dated worked example whose authority is the
endpoint page).
Two facts gain a single owner instead of two: the up{job=~"frank-.*"} scope
lives in Infrastructure/Prometheus.md, and the blackbox/Anubis probe form in
Architecture/Stale Signal Audit.md, each cited from the other page.
Voice Packs and Frank TTS carried live-state claims that contradicted the
engine page's 2026-10-18 deploy record (nothing listening on 7772/8880, model
dir absent); they now point at the pages that own host state.
A consumer that lists the 29-name af_*/am_*/bf_*/bm_* pack and refuses
narrator1 without a request reaching the façade is synthesizing in the
browser. Frank TTS's troubleshooting table gains that symptom; OpenWebUI's
See Also gains the service page.
#18 shipped DEFAULT_VOICE and #17 explained why the harness cannot name a voice from a setting, but the endpoint page still described voice resolution as if every request carried a name. One contract row, one paragraph in Naming a voice (a call that names nothing is the harness default shape, not a client bug), the env-file row now lists the default voice among its knobs, and the worklog records the rollout: both merges, the one-dispatch deploy with the 33/33 gate, the card flipping to up on the next checker tick, and the previously failing call returning 430124 bytes with the journal reading "speech voice=narrator1 (default)".
The section this PR adds claimed speech.voice was the forwarded voice key. It is not: the openai-speech transport builds {model, input, response_format, voice?} and sets voice only when the CALL passed voice_id, tts.localVoice is the local Kokoro backend key, and speech.voice is the TUI speak-aloud voice whose enum is Kokoro-only. A restarted session proved it the honest way - the tts device arrived at 7772 with no voice key and got "unknown voice None; available voices: narrator1, narrator2, narrator3" - so the fresh-process success earlier that day was the run that was told to pass voice_id, not evidence of a setting.
The law now reads: voice comes from voice_id alone, so a voice-less call is the harness default shape (the endpoint grew DEFAULT_VOICE in its own PR to answer it), and speech.voice must stay a Kokoro name - a pack name there breaks the vocalizer and does nothing for synthesis. config.yml reverted to am_fenrir on the operator box, and the worklog entry that recorded narrator1 as wiring carries the correction.
Six statements still claimed the endpoint did not exist: both vault descriptions, the two index.md bullets, the Open Notebook blocker, and the engine page deploy order that opened with "Nothing below has been run". All re-keyed to the deployed state, with the ledger the deploys printed: engine healthy 31 s after the build at 4.0 GiB resident, three voices warm, the ladder measured at 1883 ms then 1226-1283 ms (not the retired 6.2-8.0 s), and two voices rendering the same sentence into different bytes.
Adds a The harness speech role section to the endpoint page with the three wiring laws measured the day of the flip: speech.voice is the forwarded key (tts.localVoice belongs to Kokoro), an explicit speech role has no fallback candidate, and models.yml loads at process start while config.yml is read live - so a session older than the frank-tts row fails exactly like a dead endpoint. Fresh-process proof: omp -p --tools tts saved 180524 bytes with voice=narrator1 while the facade logged the matching speech line.
PR #15 corrected the engine's page, the research brief's law bullet and the
warmer's header, but the retired ladder had six more homes: both quadlet files
("6.2-8.0 s… 0.5 ms", "~6-8 s each", "paying the 5.5 s cold decode"),
`scripts/frank-tts-engine.env` (the whole law paragraph and the WARM_TEXT note),
`scripts/frank-tts.env`, the [[Frank TTS]] troubleshooting row that told an
operator to expect ~5.5 s, the research brief's consequences paragraph, and one
test comment.
All re-keyed to what was measured on the deployed container 2026-10-18: the
first synthesis of a freshly registered voice costs 1,883 ms against 1,226-1,283
ms afterwards, so the warm pass buys about 0.6 s — and the reason it exists at
all, which the pages never led with, is that the registry is memory-only: without
the hook every voice name is a 400 until somebody synthesizes. The trial's dated
worklog entry stays as written; the correction points at it.
No behavior change: comments, unit lore and docs only, so no cfhash move and no
image rebuild.
Three pages, all from faults found this session:
- `LLM/deployment.md` gains "Locally built images": which images have no
registry digest, what the `cfhash` skip is for, why it must cover every COPY'd
file (the silent shape: build skipped, /health green, old code serving), and
the base-image half of the law — the engine's `useradd`, because the pinned
ubuntu:24.04 has no `adduser`.
- `Dankclaw/Frank TTS Engine.md` troubleshooting gains the two lines an operator
actually needs: every voice cold on a fresh boot with a hand-run hook
succeeding (= `WARM_TEXT` arrived quoted, `podman exec … env | grep WARM_TEXT`
proves it), and a 502 from the façade beside a 200 `/health` from the engine
(model loaded ≠ voices registered). The stale-warmer row is re-keyed to the
new rebuild rule.
- `Dankclaw/Voice Packs.md` step 4 was wrong: it told the next agent to restart
the engine *instead of* the warm hook because the hook "only registers packs it
has not seen". It registers every pack on every run and the engine replaces a
name it already holds — `tools/tts-server.cpp:148` at the pinned commit:
"Re-registering a name replaces the previous entry." A restart is what an
engine bump needs (the registry is memory-only), never worse, only slower.
- `Dankclaw/Frank TTS.md` deploy command re-keyed to `./scripts/deploy.sh
dankclaw --service frank-tts`, the way it is actually deployed; the raw
`deploy-remote.sh` form is plumbing.
Stage 2 of the voice stack. Repo state only: nothing deployed, no host touched,
no container started.
- `frank-tts` is now registered everywhere a dankclaw service has to be:
`SERVICE_ORDER` (after `frank-tts-engine`, before the consumers), the planner's
dankclaw set, `deploy-remote.sh`'s allowlist + dispatch + a
`deploy_frank_tts` handler, and `deploy.sh`'s dispatch regex — plus the
`build_compose_image` arm naming `dankclaw/voiceservice/Containerfile`, without
which the build silently returns 0 and the unit's `Pull=never` tag never
exists.
- The unit joins TWO networks, in order: `dankclaw_default` first (default route,
where consumers and open-notebook live), then the voice bridge. `TTS_UPSTREAM`
moves from `host.containers.internal:8880` to `http://frank-tts-engine:8880` —
the actual fix for a shape that could never work, since a bridge container
cannot reach a host-loopback bind and the engine publishes nothing at all.
- `Wants=`/`After=` on the engine, never `Requires=`: the `llama-tts-cli` rollback
backend needs no upstream, so a broken engine may delay this endpoint but must
not take its unit down.
- The handler installs the engine's quadlet pair too, because a referenced
`Network=` file the generator cannot find means no façade unit is produced at
all, and `--only frank-tts` has no other way to get it. It creates
`/home/dank/voices` as the deploy user (rootless podman would auto-create a
missing bind source root-owned, which reads as "no voices"), and gates on
podman's recorded health.
- Voice naming is now written down where it can be found: a voice is a lowercase
directory name matching `^[a-z0-9][a-z0-9._-]*$`, at most 64 chars. That shape
is the intersection of two rules that disagree — the shim would accept mixed
case, the engine lowercases names — so a mixed-case pack resolves on one side
only, and the boot warmer refuses it loudly. `narrator1/2/3` are load-bearing
for open-notebook.
- Homepage card for the pair, `wip: down` and hidden, probing `127.0.0.1:7772`;
the engine gets no card because it publishes nothing and could never be probed.
- **No Prometheus job, deliberately.** No `/metrics` exists on the shim, this
Prometheus 3 refuses HTML/JSON bodies (the existing `comfyui` and `services`
probes are documented down for exactly that), and `LlmEndpointDown` is scoped
`up{job=~"frank-.*"}` — so a `/health` job would page on Signal for a healthy
endpoint. Recorded in the page instead of quietly skipped.
- Vault: new [[Frank TTS]] page linked from the index; [[Frank TTS Engine]] now
points at it for the pair's public signal; log entry for the day.
- Seam grows to 25 tests: the generator's rendering of the pair (both
attachments, first-network order, one publish, Wants-not-Requires, bounded
per-unit blocks), the env file's upstream checked as a network JOIN rather than
a string, planner separation and order, the handler's steps, the card's gating,
routing that selects both seams for either deployable.