Commit Graph
100 Commits
Author SHA1 Message Date
Frank 30e8ed55a0 docs(frank-zealot): record the ba69ea5 boot ledger and correct the carried credits
The boot of record for the new shape, from the verbosity-4 journal plus
settled nvidia-smi: target KV 14,406.00 (K f16 9,408.00 + V q8_0 4,998.00),
draft KV 2,352.00 over the same 200,704 cells x 3 seqs, recurrent state
452.25, compute 1,665.00 + 424.88 (draft 480.97 + 424.86), weights 2,283.29
+ 257.66, projector 595.29 + 248.10 + 24.93, settled 22,870 used / 1,255
free with n_slots = 3 / n_ctx_slot = 200704.

Two inherited numbers did not survive it, and both are corrected in the env,
the serve rationale and the page:

  - The ubatch 2048 -> 1024 step was quoted at "-~1,306 MiB". That was the
    786,432 / q8_0-pair arm. At THIS shape the target's compute buffers went
    2,496.13 -> 2,089.88: 406 MiB, with CUDA0 going UP 257 while CUDA_Host
    went down 663. q8_0 V (4,410 MiB, not the 3,840 previously written) is
    what funds the drafter, alone.
  - The fit gate priced 22,428 against a real 22,870: conservative by 442
    MiB, so it stays honest as written.

And one finding stated plainly: this is the first boot on this card that
misses llama.cpp's own free-memory target - the engine printed "cannot meet
free memory target of 1,867 MiB, need to reduce device memory by 102 MiB"
and settled at 1,255 MiB free, under the ~2 GiB band every previous era
held. Cheapest rungs back are CTX_SIZE=401408 or SPEC_TYPE=none, both one
line.

Probes on the same boot go in too: chat turn (448 tokens, 1,755 chars of
thinking - empty content at max_tokens 64 is the documented trap), tools
(finish_reason tool_calls, {"a":24,"b":7}), vision (512 px solid red ->
"Red", 280 prompt tokens), and the drafter's own statistics (acceptance
0.706 / 0.763, mean accepted length 2.41 / 2.53).
2026-10-19 03:38:03 -07:00
Frank a7062d1cb3 docs(frank-zealot): carry the 3 x 200,704 shape, retire the exact-KV era row
The endpoint page owns its shape: the flags row, the budget equation behind
it (25,088 B/token, 14,406.00 MiB of cache + 2,352.00 MiB of draft cache
inside 24,123 MiB addressable), the "MTP is ON" section replacing the "OFF by
measurement" one, the harness contract at the per-slot 200,704, and the
fallback rungs rewritten as deltas FROM the shipped shape.

The 2026-10-18 exact-KV boot (KV 16,384.00 / RS 100.50 / compute 1,408.06 +
1,088.07 / mmproj 868.32 charged after the fit check, settled 21,176/2,949)
and its contended rate floors move into that era's Eras row with the one-line
rollback, so the two pages that carried them collapse to one. Rates for the
new shape are deliberately absent - no bench was ordered, and the fit gate,
the health gate and the boot ledger are the evidence a swap needs.

AGENTS.md follows the same numbers: contextWindow 200704 is the per-slot
window and the trio's shape line.
2026-10-19 03:16:48 -07:00
Frank 25a8b00043 feat(frank-zealot): reslice to 3 x 200,704 with q8_0 V and MTP armed
Frank's order, 2026-10-19: --parallel 3 at 200,704 a conversation (pool
602,112), K at the engine default (f16), V to q8_0, batch 1024x6 / ubatch
1024, and the MTP drafter armed explicitly.

The three requests are one budget equation. At 8,192 elements per token per
cache the pair now bills 25,088 B/token = 14,406.00 MiB over the 602,112
pool, and the armed draft context adds its own f16 KV over that whole pool
(4,096 B/position = 2,352.00 MiB) plus buffers and recurrent cells. Dropping
V to the 8-bit class frees 4,410 MiB against exact V and the ubatch drop
hands back ~1,306 - that is what pays for the third slot AND the drafter,
neither of which exact V could fund.

The serve script's written-down fallbacks move with the env so a missing env
file cannot boot the retired shape, and the seam now: asserts the per-slot
contract (divides evenly, <= the trained 262,144), bills an armed drafter in
the VRAM fit gate, proves the gate still REFUSES both boots this shape can
walk into, pins the shipped MTP set (and the absence of --spec-draft-model,
which would load the trunk twice), and pins SPEC_TYPE=none as the rollback
that strips the whole set.
2026-10-19 03:16:35 -07:00
Frank c195c839b4 docs(agents): say a scout verdict is a lead, not a finding
The 2026-10-19 vault de-dup campaign sent four read-only scouts over the same
pages and had to parent-verify every claim: one inverted era-page law (flagged
pages stale for documenting the retired payloads they exist to record), one
listed files that do not exist, one reported duplications grep disproved, one
cited a line past the end of the file it judged. Record the rule beside the
other evidence laws so the next agent does not integrate a yield unchecked.

Refs: vault de-dup campaign, PR #24.
2026-10-19 02:04:40 -07:00
Frank 500ac2b871 docs(vault): consolidate the vLLM offload-knob claim to one owner (C2, C6)
The "`--swap-space` is removed; use `--kv-offloading-size`" claim was carried by
six pages, two of them with their own copy of the upstream source citation. It
now lives only in [[RAM CPU Offloading in VLLM]], which carries the 2026-10-19
verification against `vllm/config/cache.py` (no `swap_space`; `kv_offloading_size`
activates only with `kv_offloading_backend`):

- `VLLM Overview` (two argument tables), `VLLM Index` (parameter table),
  `DFlash DSpark Speculative Decoding`, `Shared Memory Architecture Dankclaw`,
  `Hybrid CPU GPU Processing Dankserver` -> cite the owner.
- `VLLM Index`'s example command used the dead flag; it now shows the live pair.
- `dankserver-vram-growth`: banner disambiguates the 2026-08 `frank-zealot`
  (llama.cpp on the 3090 when that card was in Dankserver) from today's endpoint
  of the same name on Dankclaw, so a retired crash signature does not read as a
  live fault on a same-card-class service.

Refs: vault de-dup campaign, clusters C2 and C6.
2026-10-19 02:01:14 -07:00
Frank 6a8cbde260 docs(vault): retire the ACTIVE labels on frank-medium era pages (cluster C5)
The root and LLM index pages still advertised two retired endpoints' pages as
live procedure:

- `frank-medium-radiance-mxfp4`: frontmatter said "ACTIVE frank-medium
  runbook" and the body opened "The ACTIVE frank-medium deployment". Both
  re-keyed to the era it ran (2026-09-21 -> 10-10), with [[frank-archon]]'s
  Eras table named as the owner of that shape.
- `frank-medium-model-swap`: read as live how-to for a retired endpoint.
  Re-keyed to historical, pointing at [[Model Swap and Rollback]] for the
  current swap law.
- Both index pages' rows re-keyed to match (rows are hand-copied from
  frontmatter - no generator - so both sites move together).

Refs: vault de-dup campaign, cluster C5.
2026-10-19 02:00:21 -07:00
Frank 73fb27254e docs(vault): era-correct the vLLM/Ling era pages (cluster C3)
- `Running Qwen3.6 27B FP8 on Dankclaw`: had no banner at all and opened as a
  live how-to. Added the RETIRED banner (iGPU endpoint today is
  [[frank-dragoon]]), era-tagged the hardware table, and rewrote the
  "primary service now runs vLLM-ROCm with Ling CIRU INT4" production note,
  which was false by three fleet generations.
- `ling-3.0-flash-vllm-plan`: "frank-dragoon is the Qwen3.8-27B PP head" ->
  past tense with the 2026-08-17 redo as the anchor and the live owner named.
- `frank-redo-2026-08-17` banner: stripped restated live shapes that had
  already drifted (radiance 1.0.13, PrismML b10754 - the fleet runs 10777)
  and the 3090-provenance clause owned by the PP deployment page; banner is
  now locator-only (names + ports + links).

Verified before cutting: 457.9 t/s and the ping-RTT figures are solo-held by
Ethernet Bandwidth Math.md (no duplicate), so both scout-reported "duplicates"
were kept.

Refs: vault de-dup campaign, cluster C3.
2026-10-19 01:59:13 -07:00
Frank e32df1b04f docs(NPU page): era-tag host-RAM and PP-rate restatements
One fact, one owner (de-dup campaign C4):

- `FastFlowLM NPU Inference.md` carried the host RAM figures three times
  (29 GiB budget input kept as era data; the 2026-10-18 `free -g` re-reads
  of 121/246 GiB cut -> [[Dankclaw System Specs]]/[[Dankserver System Specs]]).
- The 14-18 t/s PP-split decode is this page's own solo-held era measurement:
  kept, tagged to the 2026-08 era with [[frank-redo-2026-08-17]].
- Promotion section: `frank-zealot` "now runs the NPU endpoint" era-tagged
  (the name has been reused twice since; the NPU unit is `frank-npu`, masked).
- Repaired the Test Runbook splice introduced by the range edit above.

Refs: vault de-dup campaign, cluster C4.
2026-10-19 01:58:03 -07:00
Frank a518fc7c22 docs(vault): de-stale the quant and swap method pages
Parent-written (scout summaries were corroborated against the files, not
trusted): every restatement of an endpoint shape now cites the endpoint page,
and four claims contradicted by today's fleet state are corrected.

- Quantization Production: the challenger-contract line quoted the retired
  compaction-hub window; now cites [[frank-zealot]]'s Current shape. The 3090
  worked example is era-tagged. Sources listed [[frank-dragoon]] three times.
- Quantization by Hardware Architecture: said the 3090 lives on Dankserver
  (it moved to Dankclaw 2026-09-16; the Eras table owns the move) and ranked
  the retired Ling-3.0 Quark INT4 service as the ACTIVE Dankclaw workload. The
  active service is [[frank-dragoon]] on a closed `.hgn` format.
- GGUF Compatibility: Related claimed the ftype-115 ROCmFPX file was "in
  production"; that is an era now, so it points at [[kingjones-q8agent-rocmfpx]].
- Model Swap and Rollback: dropped the fragment byte-count ledger (AGENTS.md
  owns it), the Ampere FP8 hardware claim ([[Quantization by Hardware
  Architecture]] owns it), halogen's HTTP surface ([[frank-dragoon]]'s wire
  contract) and three brittle unit-file line refs (accepted loss: line numbers
  are derivable, the root-unit law stays). Fixed the per-slot example, which
  described the retired 3-slot arithmetic, and a Related bullet that called
  [[frank-archon]] retired while linking the same page twice.

Census over the diff: 1 orphan line, the deleted unit line-number refs above.
2026-10-19 01:52:30 -07:00
Frank 9f1a4e4e51 docs(dankclaw): drop the stale frank-zealot drift note
The page claimed the unit had been failed since 2026-10-18 03:40 with a
hand-run process on the port. Verified live: both units are active under
their repo units (zealot since 21:06, the deployer's 2-slot reslice).
2026-10-19 01:39:54 -07:00
Frank 4a13dcc5b7 docs(agents): say who owns a campaign ledger
The dedupe passes kept trying to port single-owner experiment figures into
endpoint Eras tables. The law now states the test explicitly: two pages
carrying one figure is the defect; a figure with exactly one holder is fine
wherever it lives.
2026-10-19 01:39:54 -07:00
Frank e7bfa82853 docs(log): compress the 2026-10-18 entries to incident + law
The 3-slot -> 2-slot reslice entry and the contended-rate entry carried the
full measurement ledger; the numbers belong to [[frank-zealot]] (shape block +
Eras rows), so the entry keeps the causal story (pool halving funds exact KV;
rates taken under a live consumer are floors) and links for the figures.
2026-10-19 01:34:06 -07:00
Frank 04f4e2c6d4 docs(vault): strip restated fleet facts from 17 technique pages
Enforces "one fact, one owner": technique pages keep mechanism, laws, traps
and verdicts; per-endpoint shapes, digests and era measurements are cited to
the endpoint page that owns them.

Also repairs the four pointers the pass left empty (each verified against the
cited owner before landing):
- EMULATION dequant doubles host RAM (118 GiB -> ~238 GiB) -> [[frank-archon]]
- 12.11 tok/s flat 304 -> 16,095 tokens -> [[Tcclaviger Fork Ops]]
- bundled-head acceptance ladder 0.739/0.554/0.427/0.340 -> [[Qwen3.8 PP Deployment]]
- llama.cpp PR #23897 dedup provenance -> [[qwen3.8-spec-matrix]]

Replaces the retired-shape illustration in Llama Cpp Serving Sizing with the
owned MiMo Q5_K_M row (6.40 GiB payload -> 20,530 MiB used), and unwraps a
hard-wrapped [[Dankserver System Specs]] link that failed link integrity.

Orphan census over the diff: 0 numbers lost (comma-insensitive match against
the whole vault plus AGENTS.md).
2026-10-19 01:33:47 -07:00
Frank eb21dbc705 Merge origin/main (PRs #21, #22) into the documentation-law restructure
Conflicts and how each was resolved:

- AGENTS.md: kept the 2026-10-18 Model Swaps law (a payload change is the trio
  edit, the deploy, /health and one probe; no bench unless Frank orders one).
  The incoming Benchmarking-Policy wording it replaced is not lost: its one
  durable fact -- on the PrismML fork build 10777 the per-request `timings`
  object only reaches you on /v1/chat/completions, because a greedy ignore_eos
  run on the raw /completion path answers 500 "Content-only format" -- is now
  carried inside "When a bench IS ordered", step 2, and in [[frank-zealot]]'s
  Contract section.
- frank-zealot-llama-prism-bonsai9-mtp.md: kept deleted. PR #22's additions
  (the exact-KV boot ledger, the contended floor rates, the /completion
  instrument trap) were ported into LLM/frank-zealot.md before the merge, which
  is where the one-page-per-endpoint law says they live.
- log.md: auto-merged, then compressed to incident plus law -- the reslice
  decision, the "a KV byte rate belongs to the payload it was measured on"
  lesson that buried the invented 26,112 MiB figure, and the instrument law.
  The boot ledger and the floor table now live only on [[frank-zealot]].
2026-10-19 01:07:54 -07:00
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
Frank 6bdfd2853e docs(vault): service and infrastructure pages stop restating endpoint facts
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.
2026-10-19 00:56:02 -07:00
Frank 219a097b39 docs(vault): one page per endpoint, forever -- 45 era pages fold into three
Every payload swap a live endpoint has ever run is now one row in that
endpoint's Eras table, with its rollback path in the same row. No per-attempt
page, no per-swap ledger, nowhere else.

- Added: LLM/frank-archon.md (radiance, 2x R9700 TP=2), LLM/frank-dragoon.md
  (halogen flash-server, 19 eras), LLM/frank-zealot.md (llama.cpp PrismML fork
  on the 3090). Each carries Current shape, Eras, Laws, wire contract, files.
- Deleted: 45 per-payload runbooks -- the frank-dragoon line (11 including the
  pre-name halogen/kingjones/kairic/CIRU/eemin/agentionai/dflash2 pages and the
  benchmark page), the frank-zealot line (llama.cpp/vLLM/ik_llama/SGLang eras),
  the frank-archon line (vLLM/FreeToken/R9V/llama.cpp/radiance eras), plus
  frank-medium, frank-npu and the ubatch/PCIe one-offs that owned nothing.
- Rewired: every wikilink and markdown-path reference to a folded page points
  at the endpoint page instead; the three Research see-also lists and the
  ubatch sweep's runbook link included. No anchored links existed in the set.
- Regenerated both index pages as pure locators -- one row, one clause, no
  numbers -- so the docs_completeness locator law holds for all 153 pages.

tests/docs_completeness.bats green: index links every page, no unresolved
links. The pages moved to .AGENTTRASH/20261018-doc-dedupe/LLM/ (gitignored,
and git history holds every byte).
2026-10-19 00:49:03 -07:00
Frank af5ddfc54c chore(scripts): env files carry knobs plus the reason, not the swap ledger
Every KEY=value is byte-identical to the previous revision (diffed against
git show HEAD: -- 28/25/20 keys). What left is the per-swap narration: the
archon header drops 207 lines of era prose, dragoon 127, zealot 104. Each
header now keeps the format lock, the digest/identity law, and one line per
knob explaining what a reader editing that line cannot recover anywhere.

The facts moved to their one owner: LLM/frank-archon.md, LLM/frank-dragoon.md,
LLM/frank-zealot.md. One stale claim dies with the trim -- the dragoon header
still said the KV pool is ONE 200,704-position budget behind 2 slots, which
contradicted its own 401,408 paragraph; the page carries the correct ledger.

Also kills two wrong arithmetic claims in the zealot header: the q8_0-V and
CTX_SIZE=401408 rungs each free 3,840 MiB at the 524,288 pool, not 8,192 and
not ~6.7 GiB.
2026-10-19 00:48:36 -07:00
Frank 61011b0a81 docs(agents): one fact, one owner — AGENTS.md carries operator-critical numbers and cross-cutting laws only
The runbook index collapses to three live endpoints plus a retired-row
locator: a payload swap is an Eras row on the endpoint's vault page, never
a page of its own. Restated shapes, digests and measured numbers are
replaced with links to [[frank-archon]], [[frank-dragoon]], [[frank-zealot]]
and scripts/<svc>.env, which is the authority for every value.

Carries the frank-zealot instrument correction from d457985: on the PrismML
fork build 10777 the per-request timings object only reaches you on
/v1/chat/completions — the raw /completion path answers 500 on a greedy
ignore_eos run.
2026-10-19 00:48:09 -07:00
Frank d5a43b7192 Merge remote-tracking branch 'origin/main' into wt/20261018-204501 2026-10-18 21:28:04 -07:00
Frank d457985b8d docs(frank-zealot): measured exact-KV boot, floor rates, chat-path instrument
Deployed shape verified live: KV 16,384.00 MiB (ledger-exact 32,768 B/token x
524,288), RS 100.50 MiB (2 cells), settled nvidia-smi 21,176 used / 2,949 free,
n_slots 2, n_ctx_slot 262144, DropInPaths empty.

Rates recorded as FLOORS: another consumer drove the endpoint through the whole
window (56 prompts on the other slot in three minutes, 96% GPU-util). Single
stream 134.1-141.5 (145.8/163.2 in a lighter minute) vs the 168.7 q8_0 record;
two-stream aggregate 256.6-265.1 vs ~300 across three; prefill 3,159.5 at
106,282 beats the 2,877 record while contended; decode at 106k depth 71.5 (new
baseline, no prior). Per-stream concurrency improved: ~130 each vs 102.8.

Instrument correction: the PrismML fork build 10777 answers 500 'The model
produced output that does not match the expected Content-only format' for a
greedy ignore_eos run on the raw /completion path, so the per-request timings
must be read off /v1/chat/completions. AGENTS.md Benchmarking Policy corrected.
2026-10-18 21:28:04 -07:00
Frank 71bc1735d3 docs(frank-zealot): re-key the exact-KV shape across the vault
AGENTS.md: topology row, the frank-zealot runbook section (shape, KV
accounting, MTP verdict, compaction-hub concurrency), and two Key
Lessons bullets -- the draft-context bill restated with the real knob
names, plus a new lesson that a quant's scale overhead belongs in its
byte rate (the 6.25 % invented by doubling 13,056.00 MiB).

LLM/frank-zealot-llama-prism-bonsai9-mtp.md: retitled to the live shape;
the 3-slot q8_0 boot ledger and its measurements are relabelled as the
arm this replaced (byte-identical numbers, honest attribution), the
corrected f16 rate derivation is added, the MTP census is labelled
q8_0-era, the fallback rungs are re-derived for the 524,288 pool, and the
boot-ledger section carries an explicit PROJECTION that must be replaced
with the measured ledger from the first f16 boot.

Pointers re-pointed (live-shape sentences only; every historical ledger
left byte-identical): LLM/index.md, vault index.md, Dankclaw/OpenWebUI.md,
and the retired-runbook "today" pointers in mimo-q5km, mimo-q6k,
qwen35-9b-q6k, swift15, sglang-mimo-v26. log.md gains the dated entry.
2026-10-18 21:02:42 -07:00
Frank 68afc6dbaf fix(frank-zealot): 2 x 262,144 over unquantized f16 KV
Frank: "shift frank-zealot to 2 parallels of unquantized kv".

Shape: CTX_SIZE 786432 -> 524288, PARALLEL 3 -> 2, CACHE_TYPE_K/V
q8_0 -> f16. The per-slot window is UNCHANGED at 262,144 (the native
trained window, and models.yml contextWindow), so what the exact cache
buys is fidelity and what it pays is one concurrent conversation.

The POOL is the term that had to move: exact KV bills 32,768 B/token at
this geometry (8 full-attention layers x 4 KV heads x 256 dim, 2 B,
K and V), so the old 786,432 pool asks 24,576 MiB against the 24,123 MiB
the driver leaves addressable. 524,288 positions = 16,384 MiB, projected
~21,200 used / ~2,900 free from the 13:19 boot ledger (only the KV pool
and one recurrent-state cell move).

Also corrects a repo-wide arithmetic error: "f16 would bill 26,112 MiB"
doubled the q8_0 ledger figure INCLUDING its 1/32 scale overhead. q8_0 is
1.0625 B/element, f16 exactly 2.0, so the true ratio is 1.882x. The wall
stands; the number was wrong.

And it corrects a false claim about the drafter: this build DOES expose
--spec-draft-type-k / --spec-draft-type-v (verified against
llama-server --help), so the draft cache is quantizable -- the
exact-KV-plus-MTP exclusion is a measurement gap, not a law, and the
1,088 MiB quantized-draft arm is recorded as unmeasured.

Seam: tests/frank_zealot_unit.bats re-pins the shape contract (2 slots,
524,288 pool, 262,144 per slot) and adds a VRAM fit gate that computes
dtype x pool + the measured non-KV ledger terms against 24,123 MiB with
>=1 GiB of slack -- and proves the gate REFUSES exact KV at the old
pool, so passing means something. 18/18 green.
2026-10-18 21:02:37 -07:00
Frank 9a17b93e8a docs(frank-tts): crosslink the browser-Kokoro rejector both ways
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.
2026-10-18 20:17:19 -07:00
Frank 3e779823e0 Merge remote-tracking branch 'origin/main' into wt/20261017-181502
# Conflicts:
#	serverAppConfigs-docs/log.md
2026-10-18 20:17:19 -07:00
Frank 30e3d36b1e docs(openwebui): the TTS voice rejector is in-browser Kokoro, not frank-tts
The `Voice "narrator1" not found. Should be one of: af_heart, …, bm_fable`
toast is OpenWebUI 0.11.4's bundled Kokoro.js validator: the enum is
Kokoro's fixed 29-voice pack, not frank-tts's registry. Frank-tts answers
200 audio/mpeg for the exact OpenWebUI body from inside the container, and
the failing click leaves no `audio/speech` line in the container log — that
contrast is the instrument. Admin config DB is correct
(engine=openai, base URL :7772/v1, model frank-tts, voice narrator1); no
container env and no user row carries an audio override, so the
`browser-kokoro` choice is per-browser storage. Fix: Settings → Audio →
TTS Engine → Default.
2026-10-18 20:15:28 -07:00
Frank 96b7687b34 docs(frank-tts): the contract table gains its Default voice row
#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)".
2026-10-18 19:32:03 -07:00
Frank b91873578b fix(frank-tts): answer a voice-less request with the declared default
The omp speech device forwards a voice only when the call passes voice_id - there is no setting that feeds one (speech.voice is the TUI speak-aloud Kokoro enum, tts.localVoice belongs to the local backend) - so every harness voiceover arrives with no voice key and the shim answered 400 "unknown voice None". Measured on the deployed endpoint 2026-10-18, from a restarted session with the speech role pointed here.

DEFAULT_VOICE makes the absent case a spoken operator choice instead of a code surprise: the pack named there answers a voice-less request and the journal line is marked (default), so an unrequested voice never reads later like a requested one. An UNKNOWN name still 400s with the list that exists - the no-silent-fallback law that this endpoint was built on is untouched, and the strict shape stays the code default (empty DEFAULT_VOICE), which the second new seam test pins. Ships narrator1 in scripts/frank-tts.env.
2026-10-18 19:21:28 -07:00
Frank 0d70f115d1 docs(frank-tts): the harness sends a voice only in voice_id
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.
2026-10-18 19:21:22 -07:00
Frank 7e391d4325 docs(frank-tts): the voice stack is deployed, and the harness speaks through it
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.
2026-10-18 19:11:41 -07:00
Frank 5768680542 fix(homepage): the frank-tts card is a real probe now
The endpoint deployed on 2026-10-18, so the admin card must carry its state rather than a placeholder: wip becomes false (the registry schema wants the key on every service; false is what the live cards use) and the description drops the "Hidden until deployed" line. showLink stays false and the card stays admin-only: an HTTP API has no page to open, and homepage-status.py counts ANY HTTP response as up, so the card is honest with no link and no /health path in the probe. The voice seam that pinned wip == down is re-keyed to the deployed contract and now also refuses a link or a user-category placement.
2026-10-18 19:11:41 -07:00
Frank 2dc31ff09c docs(frank-tts): retire the 5.5 s cold-voice claim everywhere it still lived
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.
2026-10-18 18:52:50 -07:00
Frank 0773b26559 docs(frank-tts): the cold-voice ladder did not survive the deployment
The first real boot gave the chance to measure what warming actually buys, and
the trial's numbers were wrong:

- Empty registry, register only, then time: the FIRST synthesis of a freshly
  registered voice cost 1 883 ms; the next two 1 283 and 1 226 ms. A ~110-word
  request cost 9.4 s and then 9.8 s.
- So the published 6.2–8.0 s cold / first-audio-byte 5 530 ms / later-requests
  0.5 ms ladder does not reproduce, and request time tracks the audio generated
  rather than snapshot state. The 0.5 ms figure is an internal state-restore
  cost, never an end-to-end request time.
- Warming still earns its place for a different reason than the page claimed:
  the registry is memory-only and re-registering does not warm, so a boot without
  the hook leaves the façade answering 400 for every name. The saving is ~0.6 s.

Law 1 of [[Frank TTS Engine]] is re-titled the registry law and carries the
measured numbers; its example journal line is a real one from today; the
troubleshooting row for "first request of the day is slow" — the retired law's
symptom — is replaced by the argument-cap fault that actually happened. The same
ladder in the research brief is annotated rather than erased, and [[Voice Packs]]
step 6 stops claiming the hook registers only packs it has not seen (the engine
replaces a name it already holds).
2026-10-18 18:45:31 -07:00
Frank 4235c1d4cb fix(frank-tts): send the registration body through a file, not an argument
The deploy of the newly built engine image came back `0 voice(s) warm` with
`/app/warm-voices.sh: line 152: /usr/bin/curl: Argument list too long` above
every line, and the unit was `active` with a green `/health` beside it. The hook
built `{"name":…,"wav_b64":…}` in a shell variable and passed it as ONE curl
argument; the kernel caps a single argument at MAX_ARG_STRLEN = 131,072 bytes,
and the shipped reference clips base64 to about 1.27 MB, so curl was never
executed and the hook reported the absence as "HTTP no response".

The body now goes to a mktemp file and curl reads `@file`; the audio enters
through a redirection instead of a command substitution, so the script never
holds a 1.3 MB string at all. Every pack in the fleet is ~1 MB, i.e. ~13x the
cliff, so this was always going to happen — it took the first real packs to show
it because no fixture was ever that big.

The regression test therefore ships a 300 KB pack against the seam's REAL stub
listener with the REAL curl: it fails with the same kernel error the host did
(`Argument list too long`, then the 400-for-every-name summary), and passes once
the body is a file. Proved red before the fix, green after.
2026-10-18 18:45:31 -07:00
Frank 6956189bb6 docs(frank-tts): the worklog wikilink must not straddle a line break
The vault link check reads [[Frank TTS Engine]] literally, so wrapping it made
the target "Frank\nTTS Engine" and the unresolved-link seam failed. Rewrapped,
not reworded.
2026-10-18 18:10:25 -07:00
Frank 3380b7da10 docs(frank-tts): worklog entry for the voice-stack audit
Three faults, all of the reporting-success-while-doing-nothing species: the
build that could not start (no adduser in the base), the warm pass that could
not warm (podman's --env-file keeps the quotes), and the image that would never
rebuild (cfhash over the Containerfile alone). Records the proofs, the two
behaviour seams, and what stays undone: nothing is deployed, #2 phase 1 stays
open until 7772 answers.
2026-10-18 18:10:02 -07:00
Frank 7178e1c789 docs(frank-tts): what makes a locally built voice image stale, and what the warm text must survive
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.
2026-10-18 18:07:19 -07:00
Frank 59e021da2c fix(deploy): hash every file a locally built image COPYs, not just its Containerfile
build_compose_image skips `podman build` when the installed image's `cfhash`
label matches a hash taken from the checkout now — the skip is what keeps
frank-tts-engine's ten-minute recursive source clone off every re-deploy. The
hash covered the Containerfile ALONE, so any change to a file the image COPYs
was invisible: edit dankclaw/voiceservice/app.py or tts-engine-warm.sh, re-run
the deploy, and podman reports the existing image is fine. The redeployed
container runs the previous code, /health answers 200, and the e2e gate has no
way to see it. It bit the boot warmer twice this week.

Each case arm now names its own extra=(…) — the paths its Containerfile COPYs —
and the label hashes the Containerfile plus those. The two arms are kept apart
deliberately: the façade bakes app.py and tts-start.sh, the engine bakes
tts-engine-start.sh and tts-engine-warm.sh, and comfyui/loki/alloy have no extra
files, so their hash is unchanged and they do not rebuild on this commit.
Hashing rather than word-splitting because these are repo-relative paths under
a fixed checkout root.

The seam drives the real function against a stub podman and asserts observable
output — did the deploy ask podman to build, or did it skip? — in five steps:
first deploy builds, unchanged tree does not, editing the engine's COPY'd warm
hook rebuilds the ENGINE image and not the façade, editing app.py rebuilds the
façade. Reverted scripts/deploy-remote.sh to prove it red on the old hash: the
warm-hook step is exactly where it fails.
2026-10-18 18:06:43 -07:00
Frank d336bdaa66 fix(frank-tts): make the boot warm pass survive both readers of its env
`WARM_TEXT` reaches the container **with its quotes**. The unit reads the env
file through podman's `--env-file`, which takes the literal remainder of the
line — no shell parsing — while bash `source` strips one layer. The hook
interpolated the raw value into the synthesis body, so with the quotes intact
the JSON was invalid and every voice logged "registered, but the warm synthesis
failed" while a hand-run hook (bash shape) succeeded. Reproduced both ways
against a real `podman run --env-file`, not by reading the man page.

The hook now strips exactly one matched pair — only when both ends are the same
quote AND the inner content carries another quote or a backslash, so a value
that legitimately contains quotes is never mangled — and then JSON-escapes what
is left, which is what makes a sentence like `He said "hi\" once"` arrive as
itself instead of corrupting the body.

The seam gains a test that reads `scripts/frank-tts-engine.env` BOTH ways —
`source` and the way `podman --env-file` does — and asserts the engine receives
the same sentence either way, in both shapes. A test that only sources the file
cannot see this bug, which is how it survived the first deploy.
2026-10-18 18:06:06 -07:00
Frank d135ae7f02 fix(frank-tts): create the engine user with useradd — the base has no adduser
The runtime stage of Containerfile.engine ran `adduser --system …`. The pinned
ubuntu:24.04 base ships no adduser, so the build died at that RUN line with
crun's "executable file not found in $PATH".

Verified in the base itself on Dankclaw, 2026-10-18: `command -v adduser`
prints nothing, and the exact replacement line — `useradd --system --uid 10000
--home-dir /app --shell /usr/sbin/nologin tts` — answers `uid=10000(tts)
gid=999(tts)`. Same account as before: numeric uid/gid 10000, non-login shell,
no password entry. Its one warning ("uid 10000 is greater than SYS_UID_MAX
999") is cosmetic and is what keeps the uid equal to the façade's, so the
comment says so before someone "fixes" it by dropping --system.
2026-10-18 18:05:07 -07:00
Frank c1087f214e docs(frank-tts): plain wikilink to the merged voice-pack page 2026-10-18 17:44:43 -07:00
Frank 1312c29fe2 Merge remote-tracking branch 'origin/main' into feat/frank-tts-engine
# Conflicts:
#	serverAppConfigs-docs/index.md
#	serverAppConfigs-docs/log.md
2026-10-18 17:44:31 -07:00
Frank 8fefe6e1a9 feat(frank-tts): deploy wiring for the 7772 voice endpoint, and voice naming written down
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.
2026-10-18 17:43:08 -07:00
Frank 8d76854a1e feat(frank-tts-engine): the arm A engine as a boot-persistent quadlet unit
Repo state only: nothing deployed, no host touched, no container started.

- `frank-tts-engine` on Dankclaw: `qwentts.cpp` commit
  5916bf7c79dbb4e66fc059cdc507a79a23a28bb4, resident `tts-server`, 1.7 B base
  talker Q4_K_M + Q8_0 tokenizer, container port 8880. It is deliberately NOT a
  fleet endpoint — `frank-tts` on 7772 is the endpoint consumers address.
- Publishes NOTHING. A host-loopback publish would have been useless to the
  façade (netavark DNATs published ports only, so a bridge container cannot
  reach a host loopback bind — the shape that 502'd hermes) and would have been
  the one LAN-reachable surface of an unauthenticated synthesiser. Both voice
  containers sit on one committed quadlet network, `frank-tts-engine.network`
  pinned to 10.89.5.0/24 (next free /24 after open-notebook's 10.89.4.0/24), and
  peers address the engine by container name. Diagnostics and the deployer's
  health gate run inside the netns (`podman exec`, `wait_container_healthy`).
- Cold-voice law honoured in the unit: the first request for a registered voice
  decodes its whole reference to snapshot codec state (6.2-8.0 s, first audio
  byte 5 530 ms), later ones restore in 0.5 ms, and the registry is memory-only.
  `ExecStartPost=-/usr/bin/podman exec frank-tts-engine /app/warm-voices.sh`
  with `TimeoutStartSec=900`; the `-` prefix keeps a failed warm from
  crash-looping the endpoint under `Restart=always`. It logs cold=/warm= per
  voice, so the under-a-second second request is proven on every boot.
- Thread law recorded: `tts-server` has no `--threads`; src/backend.h uses
  hardware_concurrency()/2, so a cpuset pin silently changes the thread count.
- Image builds from the pinned commit with the proven line (GGML_BLAS=OFF
  forced by this host, every GPU backend off, -j 8, no -DGGML_NATIVE because
  ggml defaults to -march=native for the building host). Two stages; the runtime
  stage COPYs both the entrypoint and the warmer, because a container that
  publishes nothing is reached with `podman exec`, not a port.
- `scripts/download-frank-tts-engine-qwentts.sh` fetches the two GGUFs with byte
  and LFS-oid checks (talker 1 219 245 248 B, tokenizer 291 150 624 B); the
  deployer gates on --verify-only instead of pulling 1.4 GiB mid-deploy.
- Registered in SERVICE_ORDER, DANKCLAW_SERVICES, the deploy-remote.sh dispatch,
  its pre-deploy requirement, deploy.sh's regex, the planner's path map (the
  whole `dankclaw/voiceservice/` tree mapped to nothing before this), and
  test-changed.sh ABOVE the `dankclaw/*.container` case whose `*` crosses `/`.
- New seam tests/frank_tts_engine.bats (19): the Quadlet generator's own output
  for network/publish/boot/warm shape, entrypoint argv driven from the tracked
  env file, the warmer against a stub tts-server over a real socket
  (registration keys, ref_text absent, late engine, hung synthesis, name
  refusal), the fetcher's verifier, the pinned cmake line, and both routing
  tables. Suite: 422 passed.
- Vault: [[Frank TTS Engine]] linked from the index; log.md entry for the day.
2026-10-18 17:26:50 -07:00
Frank 2427fc448f docs(voice-packs): runbook, index link, and the log entry
The page a new operator needs: what the three required files are and who
reads each one, how a pack is built (with the extractor's usage text
verbatim, because the flags are not guessable and there is no -o), the
10-second gate and where it fires twice, how to add a fourth voice
including the warm hook that registers a pack without restarting the
engine, and the engine-bump procedure for when the artifact sizes move.
Plus the host census as of 2026-10-18 — the trial packs carry no
artifacts and their manifest is the trial's own schema, so the builder's
first real run is a --force migration — and the three gaps left open on
purpose: no curated ICL transcript, no 16 kHz variant, no loudness pass.

Linked from the Dankclaw services list; log entry appended oldest-first.
2026-10-18 17:25:34 -07:00
Frank 3824d03ac5 Merge remote-tracking branch 'origin/main' into feat/voice-pack-builder 2026-10-18 17:18:31 -07:00
Frank 042003a969 feat(voice-packs): tracked tunables, the bats seam, and its wiring
scripts/voice-packs.env carries the decisions worth a diff and nothing
else: the pack list, each voice's source file, crop offset, crop length
and pinned source digest, the extractor path with its engine repo and
commit, and the artifact arithmetic. Paths and numbers only — the pack
directories themselves stay host state under /home/dank/voices.

tests/voice_pack_unit.bats pins behaviour, from inputs to observable
output: a crop inside the first ten seconds exits nonzero naming the
invariant and creates nothing, a legal one builds the whole pack, the
reference comes out 24 kHz mono inside 18-25 s (ffprobe, on a locally
generated tone), the manifest round-trips through jq with offsets and a
sha256 that matches the source bytes, a re-run without --force leaves
every byte untouched, --force retires rather than deletes, --dry-run
needs no extractor at all, and a stub extractor that changed its artifact
size cannot publish a voice. No engine binary, no GPU, no network, no
fleet host: qwen-codec is stood in by a stub with the real tool's I/O
shape, the way frank_tts_unit.bats drives its backends.

test-changed.sh routes both new paths to that seam instead of letting
them land on the generic scripts/* syntax fallback.
2026-10-18 17:18:30 -07:00
Frank 2242fcfec0 feat(voice-packs): builder — crop, pinned artifacts, manifest, 10 s gate
Turns a source recording plus a crop offset into the pack frank-tts
resolves by name: reference.wav (24 kHz mono pcm_s16le, 18-25 s), the
engine's own reference.spk / reference.rvq made from it, and manifest.json
provenance (source path + sha256, crop start/end, sample rate, duration,
tool, engine commit, voice name).

The extractor is qwen-codec from the pinned qwentts.cpp tree, and the
flags are its own usage text, not a guess: --model <codec gguf>, --talker
<base talker gguf>, -i <wav>, optional --device; there is no -o, so it
names both artifacts next to the input with the extension swapped, which
is why the pack's WAV is staged under its final stem.

The 10-second source-material invariant is enforced, not annotated: an
offset inside the forbidden region exits 1 before a directory is created,
and the gate is asserted a second time from the manifest that was just
written, together with the recorded sha256. Artifact sizes are checked
against the codec's own arithmetic (8,192 B x-vector; 22 B per 12.5 Hz
frame), so an engine bump fails the build instead of shipping a pack the
engine rejects at synthesis time — and the crop is refused up front when
it is too short to fill the .rvq window.

reference.wav is the file the façade gates on, so it lands last, after
the artifacts and the manifest: a voice name never exists on disk without
its artifacts behind it. Nothing is ever deleted — an existing pack is
refused unless --force, which retires it whole to .AGENTTRASH/ — and
--dry-run runs every step except the extractor, so the seam can test the
gate without the engine.
2026-10-18 17:18:21 -07:00
Frank 1d082a7f1d Merge remote-tracking branch 'origin/main' into docs/tts-engine-trials
# Conflicts:
#	serverAppConfigs-docs/log.md
2026-10-18 16:54:13 -07:00
Frank 5c8ac208a0 Merge remote-tracking branch 'origin/main' into docs/tts-engine-trials 2026-10-18 16:51:01 -07:00
Frank dbc2c67bcb docs(tts): record the arm A engine decision and the loopback-publish law
Frank heard every audition pair and passed it, so likeness stopped being the
tiebreak and the engine was picked on the live-output criterion: arm A
qwentts.cpp 5916bf7, resident tts-server, 1.7 B talker, llama-tts-cli kept as
the rollback. Records the discriminators (first audio byte, warm RTF, spread),
the three consequences that are configuration (boot-warm every voice, no cpuset
pin, WAV-only registration), the two follow-ups left open on purpose, and the
corrected addressing law: a host-loopback publish is unreachable from a bridge
container, so the pair shares one quadlet network.
2026-10-18 16:51:01 -07:00
Frank e030486ce6 docs: pin cwd meaning in attribution footer
Mirror of command-center clarification: first dogfooding round stamped the
build worktree instead of the session's own working directory. Rule text
now says which one, and to note the build worktree in the body.
2026-10-18 16:41:21 -07:00
Frank d89ae23195 docs: attribution footer for machine-opened issues and PRs
Mirror of dank/command-center's rule (there: PR off docs/session-trailer-rule).
Every agent-opened issue/PR body ends with cwd + omp session id so machine-filed
artifacts trace back to the box and session that produced them.

Refs #11 (command-center tracker)
2026-10-18 16:32:59 -07:00
Frank 114dd7a93e fix(open-notebook): size the TTS clock outside frank-tts's own ceiling
The env file first landed with ESPERANTO_TTS_TIMEOUT equal to SHIM_TIMEOUT=600.
Equality is a race: both budgets expire at the same instant, so the case the
number existed to prevent — the worker abandoning a synthesis frank-tts is still
finishing — is exactly what happens on the tail of a long line. Nested budgets
go inside-out, which is the rule scripts/frank-tts.env states for its own
LLAMA_TIMEOUT; the client waits one round-trip longer than the server, so a
frank-tts give-up arrives labelled as frank-tts's error. 660.

The seam's assertion moves from equality to `>` for the same reason, and now
also holds ESPERANTO_LLM_TIMEOUT under upstream's 600 s web-UI patience — above
it the failure surfaces as a UI timeout with no name on it. The scriptwriting
number itself stays at the app's 180 s default: frank-zealot answers an outline
or a transcript well inside it, which corrects the earlier commit message's
claim that both timeouts were raised past upstream's 300 s.
2026-10-18 16:31:49 -07:00
Frank e2393bacad docs(open-notebook): runbook, episode-destination interim, and the log entry
Page registered in the vault index (the docs-completeness seam fails an
unregistered page) and logged in the bundle change log.

What the page owes the reader: the provider contract (frank-zealot for scripts
with a /v1 base URL, frank-tts for voices with 1-4 speakers mapped to the
narrator1/2/3 voice packs typed as free text), why Test Connection and Discover
Models fail cosmetically against frank-tts rather than being a fault, the
encryption key as the precondition for saving any credential, and the honest
blocker — frank-tts is not deployed and nothing listens on 7772, so a generation
run writes its script and fails on the first voice.

Episode destination is stated as interim and cites Gitea issue #7 (human-only,
agents must not work it): the app keeps its named volume, one bind mount puts
finished MP3s under /home/dank/media/podcasts, and deleting that one line is the
whole rollback. Rollback section also covers the full undo, including that the
podman network only disappears once the units are gone.
2026-10-18 16:27:03 -07:00
Frank ff11c37ee0 test(open-notebook): seam for the units, the env guards, and the wiring
Structure only: what the units declare and how the scripts route them, never how
the app behaves inside a container. 19 tests.

The ones carrying real weight: the app unit has no publishable 8000 anywhere and
the database unit publishes nothing at all; the API remap off host 5055 is only
correct if API_URL names the same host port, so the seam parses the PublishPort
and compares; each unit reads the repo tunable file before the host secret file,
because podman's last-match-wins makes the order load-bearing; both health
probes are argument-only (a shell string there is a unit that starts dead); the
database password appears only as an env reference, never in Exec=; and the
episode host bind appears exactly once, since a second mount would silently
shadow the first.

The deployer assertions read the functions themselves: the gate URL path is
/health on the remapped port and must not name 5055, and the seed function
contains the blank-secret refusal plus the no-clobber test. No test asserts a
made-up endpoint or a cloud URL, and nothing touches the network.
2026-10-18 16:27:03 -07:00
Frank 0fe23c5233 build(scripts): route open-notebook paths to its own seam
The case has to sit where it does: the dankclaw/*.container glob's `*` crosses
`/`, so placed below it, dankclaw/open-notebook/quadlet/open-notebook.container
matched that generic case and ran the open-webui/pihole/searxng seams instead of
its own. Same reason the env path is grouped here — scripts/*.env would otherwise
send it to the generic env seams.
2026-10-18 16:27:03 -07:00
Frank 716eefef5d feat(homepage): open-notebook card, hidden until the service is deployed
Internal-only: port 8502 direct, no vhost field, no Caddy change — exposure
stays behind an explicit decision, and the app carries its own password.

urlStatus=hidden is deliberate: the homepage probe runs on the gateway and the
pair is not deployed, so a live card would render dead on day one. Flipping it
to live is the only homepage change the deploy needs; the seam asserts the
hidden state while the service is repo-only and requires the required card
fields either way.

mono-on.svg follows the mono-* icon set the sibling cards use, so the card does
not fall back to the generic dot.
2026-10-18 16:26:52 -07:00
Frank 8816a9d498 feat(deploy): wire open-notebook through both deployers, gated on secrets
Registration is load-bearing, not cosmetic: deploy-plan.py refuses any service
it does not know, so SERVICE_ORDER + DANKCLAW_SERVICES are what let `--only` and
`--auto` address the pair at all; the dispatch case in deploy-remote.sh runs it;
the requirement in check_prerequisites gates a manual deploy; and the dispatch
regex in deploy.sh's host-mode branch decides whether a changed path dispatches
a service or falls through to "no service mapped" — an omission there is silent,
which is exactly how the AGENTS.md deployer-registration law was earned.

The deploy seeds /home/dank/Containers/open-notebook/env (0600) from the host
env and refuses to start the pair while OPEN_NOTEBOOK_ENCRYPTION_KEY or
OPEN_NOTEBOOK_PASSWORD is blank: without the key no provider credential saves
and without the password the API — including the episode-audio route — is open
to the LAN with no auth. A seeded file is never rewritten, so re-deploying
cannot clobber a rotated key, which would silently make every stored credential
undecryptable. SURREAL_PASS is derived from SURREAL_PASSWORD in the same write.

The gate names paths that answer, not invented ones: the database is judged by
its own container health status (/surreal is-ready, verified against the pinned
digest) and the app by wait_for_health on /health at the published 5056 mapping —
the route is upstream's and is on the auth middleware's exclude list, so it
stays 200 after the password is set. 180 s covers Next.js build + startup and a
degraded-but-listening first boot, matching the sibling LLM deploys.

Both digest pins are checked against local storage before restart, because
Pull=never means a missing image would otherwise surface as a unit stuck in
"Start request queued" (the 203/EXEC trap's sibling). The interim episode
directory is created before the app starts: a missing bind source makes podman
create it as root-owned and the app's mkdir would then fail.
2026-10-18 16:26:52 -07:00
Frank 032229c1e2 feat(open-notebook): env contract — CPU guard, credential store, auth posture
The two knobs that keep the box alive: TTS_BATCH_SIZE=1 and
OPEN_NOTEBOOK_WORKER_MAX_TASKS=1. Upstream ships 5 x 5; frank-tts spawns one
~8 GiB, 8-thread engine process per synthesis request on a machine that also
serves two LLMs, so a five-way fan-out is a self-inflicted shutdown. The worker
knob is real, not decorative: the image's supervisord expands
${OPEN_NOTEBOOK_WORKER_MAX_TASKS:-5} into the worker command
(supervisord.conf:25).

Both ESPERANTO_* timeouts go above upstream's 300 s defaults, sized for CPU
synthesis (SHIM_TIMEOUT=600, LLAMA_TTS_THREADS=8) — one episode is dozens of
per-line /audio/speech calls and a timeout kills the episode, not the line.

API_URL is forced for the 5055 -> 5056 remap; SURREAL_URL addresses the database
by container name; CORS_ORIGINS is origin-form (scheme://host:port), the form
the app compares against. OPEN_NOTEBOOK_PASSWORD stays absent here on purpose:
the app treats a set-but-empty value as auth-on.

Secrets stay placeholders in the tracked file and in dankclaw/.env.example, which
lists SURREAL_PASSWORD rather than SURREAL_PASS — the deployer derives the
surreal CLI's spelling from it when it seeds, so the app and the database cannot
be handed different values.
2026-10-18 16:26:43 -07:00
Frank 09b0badaf5 feat(open-notebook): Quadlet pair — app + SurrealDB on a private bridge
Two containers, because that is what upstream runs (docker-compose.yml:6,13):
the app image under supervisord, plus SurrealDB. The `v1-latest-single` tag is
not in play — it bundles its own database, is deprecated upstream, and would
give the host a second hidden DB.

Both images are digest-pinned with Pull=never and a documented one-time pull,
the frank-dragoon posture: a Quadlet on :latest re-resolves on the first restart
after an unrelated daemon-reload, and the failure is silent.

Port map paid for by what is already on the host: seerr holds 127.0.0.1:5055
(since 2026-10-14, live `ss -ltn` agrees), so the API leaves on 5056 and the env
file has to set API_URL to match, because the frontend derives the API URL from
the window origin when it is unset. 8502 publishes on 0.0.0.0 — the service
carries its own password and the episode-audio route is unauthenticated. 8000 is
published nowhere, where upstream exposes it on loopback with root credentials.

The app joins dankclaw_default first and the private systemd-open-notebook
second: it reaches the published fleet endpoints through the former
(host.containers.internal, the arrangement Prometheus is proven against) and
finds its database by container name through the latter. Generated --network
flags keep file order, so the tunable file is listed before the host secret file
in both units — podman applies last-match-wins.

No UserMapping: the app runs as root in its namespace (no USER in the Dockerfile)
and writes only paths owned by that root. Exec= carries no database credential —
`exec start --user --pass` puts it in world-readable argv; the image's own
entrypoint passes it on stdin, so only the container env holds it.
2026-10-18 16:26:37 -07:00
Frank 0e7936f94e docs(tts): record the measured CPU engine A/B behind frank-tts
Both candidate :8880 engines were measured on Dankclaw's CPU (no GPU,
no container, nothing deployed). arm A qwentts.cpp 5916bf7 resident at
RTF 0.55-0.57 warm / 0.67 ICL / 1.65 cold; arm B mainline llama-tts at
0.93-0.99 with a +-0.25 spread. Records the four configuration laws the
trial produced (boot-warm every voice, .spk+.rvq replay is byte-identical,
streaming != buffered, /v1/audio/voices rejects a lone spk_b64), the
likeness measurement's resolution limit, and re-keys the stale
8890/voiceshim shape on that page to frank-tts on 7772. The engine pick
stays open: the likeness metric cannot rank clones, so the audition clips
decide.
2026-10-18 16:19:51 -07:00
Frank bf29518a15 Docs: PRs get opened in Frank's browser the moment they're filed
Fleet law alignment with dank/command-center: no PR-hunting in Gitea.
tests/run.sh ALL TESTS PASSED before commit.
2026-10-18 15:41:14 -07:00
Frank 169dbe2f38 Docs: codify trunk-based git workflow in AGENTS.md
The section mandated a source-test-push loop but never stated a branch
regime - agents pushed straight to main. Fleet alignment (operator
decision 2026-10-18, mirrors dank/command-center): main is the only
long-lived branch; branch fresh off origin/main per work item; PRs
target main with --no-ff; merge only on Frank's explicit OK; tags on
main. Their tests/run.sh suite: ALL TESTS PASSED before push.
2026-10-18 15:27:53 -07:00
Frank 4a6d5b8191 docs(log): frank-tts phase 1 landed as PR #4; PTQ1_0 provenance line finished
The dragoon-window provenance bullet still said the zealot arm owed a
measurement — it does not: the numbers are recovered and in the fetch script and
runbook. Adds the frank-tts phase-1 entry (naming law, port 7772, voice-pack law,
384/384 suite, PR #4 merged as 49c4e19, and the measured Gitea merge-field law).
2026-10-18 14:12:32 -07:00
Frank 31266fb7f3 feat(frank-tts): cloned-voice speech endpoint — OpenAI-shaped facade, voice packs by name
Phase 1 of the voice-over goal (Gitea issue #2). One stdlib-only HTTP facade in
front of whichever TTS engine wins the engine decision: POST /v1/audio/speech
with voice=<name> answers audio in THAT voice, where a name is real exactly when
/voices/<name>/reference.wav exists — an unknown name is a 400 listing the
voices that are, never a silent fallback (a wrong-voice endpoint stays wrong for
weeks). voice_id works as the alias the omp harness sends; response_format=mp3
is really mp3 (ffmpeg re-encode); oversized input is refused with 414 instead of
half-narrated; /health reports the backend and the resolved voice list.

Two backends behind the one surface, chosen by TTS_BACKEND: omni-http forwards to
an OpenAI-shaped upstream carrying the voice as base64 reference_audio, and
llama-tts-cli runs a local binary per request with --tts-speaker-file at nice 19
so narration never starves the text endpoints. Both are driven in the seam
through stubs — no GPU, no containers.

Named frank-tts, a first-class fleet model endpoint: port 7772 in the 7770/7771
family, alias naming per the StarCraft law, rootless boot-persistent self-healing
quadlet with a read-only /home/dank/voices mount. Voice-pack law recorded in the
env header: reference.wav PLUS the engine's own speaker artifact (required, so a
voice is reusable in other runtimes), plus manifest.json provenance with offsets
always past the first 10 s of the source file.

Not deployed: the engine decision, the pack builder, deployer/homepage/Prometheus
registration and the omp models.yml runner row are phase 2.
2026-10-18 14:06:06 -07:00
Frank 06a9cb60eb docs(frank-zealot): PTQ1_0 was ordered, measured and approved — not an unfinished arm
Frank's 02:36 instruction (archive 01a14c93) ordered the 27B PTQ1_0 arm at
2 x 200,704 with the Q8 mmproj and batch/ubatch 3072/1536; it served
production 03:14:39-03:38:57 (pid 191358, journal type ptq1_0: 402 tensors).
Recovered its numbers from that process's own print_timing lines: ~48 t/s
single-stream decode, 25-27 t/s per stream at two streams, prefill 1,295 t/s
at 30,637 -> 943 at 97,856 -> 801 at 141,365. It bought the promised 1,201 MiB
and lost 37% of decode against the PQ2_0 record, so the publisher's Ampere
warning generalizes to GA102; Frank scratched the 27B plan at 03:33 for the 9B.

Every 'unfinished experiment' claim is re-keyed, and the AGENTS.md law gains
the durable lesson: a bench verdict that never reaches the repo is
indistinguishable from a rogue arm.
2026-10-18 13:50:19 -07:00
Frank e7841bf9d6 docs(fleet): halogen owns no timing instruments; warm-cache cost measured after the pool raise
Post-deploy probe on frank-dragoon: one 101,987-token prompt through
/v1/chat/completions took 81.5 s cold (1,251 t/s of prefill) and 0.3 s on two
repeats — HALOGEN_CACHE_INPLACE=1 keeps the KV rows in the pool, so doubling the
pool evicted nothing the prompt cache needed. The RAM price is still real:
MemAvailable 26 -> 22 GiB.

Census of the same engine's surface corrected the Benchmarking Policy, which
quoted llama.cpp instruments at a halogen endpoint: frank-dragoon answers
/health, /metrics, /v1/models and POST /v1/chat/completions and 404s /ping,
/props, /completion and /tokenize, and no response carries Server-Timing. The
/completion `timings` object and the ttft;dur= header are frank-zealot's; on
frank-dragoon the instrument is wall clock plus usage.prompt_tokens.
2026-10-18 13:36:35 -07:00
Frank c0759492e6 docs(frank-dragoon): the 401,408-position pool is live; every figure re-keyed to the boot ledger
Deployed 2026-10-18 (e2e 33/33). The boot ledger prints
`reserving the KV pool, 401408 positions (about 11.0 GiB)` and
`62.9 GiB of weights locked … 11.0 GiB of KV pool, 5.7 GiB of working memory,
79.6 GiB in all`, so the ~10.8 GiB that every page quoted from the
~28 KiB/position arithmetic is replaced by the reservation the engine actually
makes (11.0) and its preflight bill (11.3). Host MemAvailable fell 26 -> 22 GiB
across the raise: the FLAGS.md collision (pool RAM is n-gram page cache) paid in
full, so the warm prompt-cache ratio is flagged as the number to watch.

All "committed but not yet deployed" clauses retired: AGENTS.md topology row,
KV-pool law and memory ledger, frank-dragoon.env lore, both index pages, the
iommu page, and the runbook chronology (third production boot recorded).

The sweep also caught the LLM index still describing frank-zealot as
Bonsai-2-27B at 2 x 200,704 in its intro and topology diagram - re-keyed to the
live 9B: 8 full-attention + 24 Gated DeltaNet + blk.32 MTP, 3 x 262,144,
tensor type 141.
2026-10-18 13:33:04 -07:00
Frank 695a5f3281 fix(frank-zealot): key the ledger to the production boot; record the arm drop-in hijack
The cutover deploy's FIRST boot was not the shipped payload: a stale
frank-zealot.service.d/ptq1.conf from the bench window still pointed
FRANK_ZEALOT_ENV_FILE at /tmp/zealot-arm-current.env, so the unit booted the
unmeasured 27B PTQ1_0 arm (3 slots of 133,888, 21,188 MiB) while /health
answered 200, /v1/models echoed the alias and the e2e gate passed 33/33.
Drop-in retired to ~/.AGENTTRASH/20261018-zealot-ptq1-arm/; the 13:19 restart
is the shape the docs now quote (n_ctx_slot 262144, KV 13,056.00 MiB,
settled 17,922/6,203).

Corrections the production ledger forced:
- RS buffer is 150.75 MiB (3 cells) on the shipped spec-off shape; 452.25 MiB
  belongs to the SPEC-ARMED arms, where MTP depth 2 bills three seqs per slot.
  The drafter triples the recurrent state on top of its 3,072 MiB of f16 KV.
- Decode measured 168.4-168.7 t/s single-stream and ~300 t/s aggregate over
  three streams (102.8 each). Wall-clock timing of a generation that stops on
  its own reads 4x low (72 t/s for a 24-token countdown) - measure with
  ignore_eos and read the engine's timings.
- OpenWebUI stores both decoration values inside JSON columns (params/meta),
  and frank-dragoon's context.length had drifted to 262,144 against its own
  200,704 ceiling; both re-keyed in the live DB.

New law in AGENTS.md: after deploying a recently-benched LLM service, check
DropInPaths, then /proc/<pid>/cmdline, then the ledger. Health checks cannot
see the payload.
2026-10-18 13:27:27 -07:00
Frank e7f0d93c86 docs(vault): fleet-wide fact re-verification pass (67 pages)
The docs-fact census wave re-read the live hosts and re-keyed every page
it could: runtime/payload identities, ports, sizes, ledger figures, unit
state, and the retirement banners that drifted as the fleet moved. The
wave was interrupted mid-run, so this commit carries only what passed the
integrity gates: every touched page parses (front matter or H1+status
convention intact, balanced fences, no truncated tables, trailing
newline), the docs_completeness and link-integrity seams are green, and
bats is 384/384.

Numbers that this session proved on live hosts are keyed to the measured
value, not the page's earlier claim.
2026-10-18 13:15:43 -07:00
Frank 466fd477d8 feat(frank-zealot): Bonsai-2-9B PQ2_0 with the MTP head at 3 x 262,144
Swap the 3090 endpoint from Ternary-Bonsai-2-27B PQ2_0 (2 x 200,704,
fork build 10754) to Ternary-Bonsai-2-9B PQ2_0 with the MTP head IN the
file, three conversations at the trained 262,144 window
(--ctx-size 786432 --parallel 3), on vendor fork build 10777 -- the
minimum build that pairs MTP with a quantized V cache. q8_0 K + q8_0 V
is the only KV that fits: 13,056 MiB against a 26,112 MiB f16 pair.
Settled boot 17,876 used / 6,249 free.

MTP is armed and then DECLINED by measurement: the draft context bills
its own f16 KV over the whole pool (3,072 MiB) and at UBATCH_SIZE=2048
that halves decode (167.2 -> 109.2 t/s). The documented MTP arm
(SPEC_TYPE=draft-mtp + UBATCH_SIZE=1024, 213.5 t/s) is recorded in the
env header rather than left to rediscovery.

Two bugs the trial caught: ${SPEC_PRESENCE_PENALTY:=0.0} was a
default-assignment, so the env value always won and the no-presence-penalty
MTP arm was unreachable (now ${VAR=0.0}); and the Q8_0 mmproj is the
publisher's requirement for concurrent image requests, which matters at
--parallel 3.

contextWindow is now 262144 -- the widest in the fleet -- so :7771 is the
compaction hub. 27B runbook re-banned RETIRED with its bench-window
section closed; indexes, homepage card, OpenWebUI doc and the worklog
re-keyed.
2026-10-18 13:15:18 -07:00
Frank 2b127163c8 feat(frank-dragoon): double the KV pool to 2 x ctx for slot parity
frank-dragoon's total in-flight context was HALF frank-zealot's: zealot runs
one 401,408-position pool split into two 200,704 conversations, while dragoon
ran ONE 200,704-position pool behind its two slots, so a single maximum-length
request filled it and two long conversations had to stay under ~100k each.

HALOGEN_KV_POOL_POSITIONS 200704 -> 401408 = slots x ctx, the top of the seam's
legal band (ctx <= pool <= slots * ctx). The PER-REQUEST ceiling does not move:
HALOGEN_CTX stays 200704, because the checkpoint's native window is 262,144 and
the engine's own 400 text refuses to exceed it without HALOGEN_ROPE_YARN. What
this buys is concurrency, not a longer conversation, which is why the harness
contextWindow (155648 = ceiling - 45056) is untouched.

Cost: ~10.8 GiB of KV where 200,704 cost 5.5 (262,144 cost 7.2; the
2026-10-06 shape's 524,288 measured 14.6), taken from the page cache the 51.2 GB
n-gram table reads through — the warm prompt-cache ratio is the number to
re-measure after the boot. Engine-side total goes 74.1 -> ~79.4 GiB of 121.2.

Also carries scripts/download-frank-zealot-bonsai2-ptq1.sh: the PTQ1_0 candidate
payload fetcher (5,946,648,928 B, LFS sha256-verified, mmproj hardlinked), with
the publisher's own throughput table in its header — the packing is smaller but
the card measures it SLOWER on Ampere, so the swap is decided on our card, not
on the card's A100 row.
2026-10-18 03:05:09 -07:00
Frank a4fe5f6839 fix(frank-zealot): serve /metrics so Prometheus can see the endpoint
The scrape has never worked: lastError is "server returned HTTP status 501 Not
Implemented" on http://host.containers.internal:7771/metrics, and the oldest
retained sample of up{job="frank-zealot"} (2026-10-11) is already 0 -- the target
has been red across four payload generations while /health answered 200 the whole
time, keeping the critical LlmEndpointDown alert permanently active. llama.cpp
gates /metrics behind --metrics; halogen serves it natively, which is why the
asymmetry looked like a 3090-only problem.

METRICS=1 in the shipped env, --metrics reaches the argv through EXTRA_ARGS, and
the seam asserts both directions (default ships the flag, METRICS=0 strips it).
Diagnosed via /api/v1/targets lastError rather than the alert list, which is also
the lesson: Alertmanager here has no persistence, so a reboot re-stamps startsAt
and a weeks-old fault looks fresh.
2026-10-18 01:52:19 -07:00
Frank 34a30d8505 docs(iommu): record the executed flip to iommu=pt and its measured cost of nothing
Window 01:24-01:35. Byte-identical A/B (rebuild proved it: same 1,287,656 B corpus,
same billed token counts 59,024 / 21,568): cold prefill 1,585.1 -> 1,616.0 t/s
(+2.0 %), warm 208,531 -> 214,131, needle hit/hit both boots. The 13-16 % that
justified amd_iommu=off since 2026-09-11 does not exist on this host; the decode
delta (-9.7 %) is instrument noise -- three repeats of one request on the same boot
gave 31.8/46.9/47.8 t/s.

NPU precondition restored: amdxdna bound 0000:c6:00.1 (driver 0.7.0, firmware
amdnpu/17f0_11/npu_7.sbin), group 26 identity, PASID width 10, /dev/accel/accel0
root:render with dank in render. No endpoint deployed, so frank-npu stays retired
-- only its rationale changes. Gates: 0 failed units, 32 groups (31 identity + the
SD reader's DMA quirk), iGPU c5:00.0 identity with ATS, dragoon ledger unchanged
(74.1 GiB, no clamp, listening at 39.8 s).

Deferred on purpose: the pp_dpm_fclk pin (changes iGPU timing for everyone, nothing
co-runs yet) and PerfOpt (7.0.0-38 has zero perfopt symbols, 26.04 caps at 7.0.0-38.38).
Re-keyed: decision record banner/front matter/verification/revert + new execution
section, frank-npu rationale, AGENTS key lesson, both index pages.
2026-10-18 01:34:17 -07:00
Frank 5ad28c19d8 feat(scripts): rebuild the frank-dragoon eval stage after a reboot
/tmp is tmpfs: the 2026-10-18 IOMMU reboot deleted /tmp/halogen-eval mid-A/B and
the off-arm numbers nearly went unreproducible. One command restores the corpus
(wikitext-2 raw test, blanks dropped, 2891 lines / 1287656 B -- the byte count is
the identity check because battery.py sizes haystacks in characters), the 512px
probe PNG, and battery.py itself; WARM=1 also pre-reads the 51.2 GB n-gram table
so a cold page cache cannot masquerade as a measured regression. Must run through
a login shell: uv lives in ~/.local/bin.
2026-10-18 01:34:17 -07:00
Frank 9d617ab010 fix(scripts): report the grub.cfg default entry, and stop claiming what was checked
The pending-entry grep anchored on "menuentry 'Ubuntu'" with -A4 and matched no
linux line, so the script printed nothing where it should have shown the next
boot's command line; the PENDING line then said "is in grub.cfg" when the loop
only ever compared the tracked file against /proc/cmdline. Now the default
entry's linux line is parsed with awk (first linux line of the first Ubuntu
menuentry, which is what GRUB_DEFAULT=0 selects), its iommu token is printed,
and a mismatch against the tracked file warns by name.
2026-10-18 01:24:03 -07:00
Frank 1b373f3559 feat(dankclaw): boot with iommu=pt — identity DMA, IOMMU stays armed
The 2026-09-11 trade bought a vendor-claimed 13-16% prefill figure that was
never measured here; the 2026-10-18 research (same decision record) shows what
identity mode actually costs the iGPU is the 1:1 page walk plus ATS/PRI/PASID/
GCR3 -- worth 2-4% on this silicon class, and removable outright by AMD PerfOpt
when the distro ships Linux 7.4. Meanwhile pt is what err=-19 asks for: the
XDNA2 NPU binds its own IOPT_TYPE_SVA domain and only needs the IOMMU enabled.

Measured off-vs-pt prefill arms follow in the docs commit; frank-archon
(Dankserver) is untouched.
2026-10-18 01:22:59 -07:00
Frank 47a5f5582e chore(dankclaw): track the host GRUB command line with an installer
/etc/default/grub was host-only state while the IOMMU boot line was the most
consequential kernel decision on the box (the 2026-09-11 amd_iommu=off adoption
lived only in the decision record). Fetched byte-exact (1581 B) and paired with
scripts/set-dankclaw-bootline.sh: refuses any host but dankclaw, shows the diff,
installs 0644 root:root, runs update-grub, names the pending token, and reboots
only on --reboot. The deployer plans nothing under dankclaw/host/ by design —
kernel state is not a container restart.
2026-10-18 01:22:49 -07:00
Frank 9b372acde4 docs(iommu): correct the identity-mode cost claim; pt is most of the fix, PerfOpt the last part
Reading patch v2 1/2 verbatim: amd_iommu_enable_perfopt() rehoming needs the
device ALREADY on the identity domain and switches ATS/PRI/PASID/GCR3 off via
the skip_caps path. So an iGPU in identity mode still pays a 1:1 page walk plus
those four capabilities until PerfOpt exists -- iommu=pt recovers most of the
gap, PerfOpt the last part. The first pass claimed pt alone already avoids the
cost; that was wrong and is retracted here and in the posted issue comment
(edit 23).

Also re-keys frank-npu.md, which carried the dead law 'iommu=pt is not a middle
ground' as its retirement rationale: identity mode + SVA is exactly the revival
path (amdxdna binds its own IOPT_TYPE_SVA domain), and the cost is single-digit
percent on this silicon class. Procedure step 2 now demands a local off-vs-pt
measurement instead of inheriting the vendor's 13-16%, whose slow-arm boot line
our record never identified.
2026-10-18 01:03:19 -07:00
Frank 7643381b9b docs(iommu): AMD PerfOpt removes the iGPU translation penalty (issue #3 research)
Records the 2026-10-18 answer to 'does a recent kernel change retire
amd_iommu=off': PerfOpt (iommu tree 2026-09-24, Linux 7.4) stops GPA->SPA
translation for the privileged iGPU while the IOMMU stays enabled for every
other device, the NPU included. Local census proves the fix is absent here
(zero perfopt symbols in System.map-7.0.0-38, archive caps at 7.0.0-38.38),
and the September 13-16% figure was translated-domain cost, not strict-flush
cost -- lazy DMA is the Ubuntu default, so iommu=pt already avoids it. The
corrected procedure therefore flips to iommu=pt now and treats PerfOpt as a
2-4% later bonus on this silicon class (Phoronix desktop arm), with the NPU
legal under identity mode because amdxdna binds its own SVA domain.
Re-keys the AGENTS.md key-lesson bullet; no host change.
2026-10-18 00:57:42 -07:00
Frank 1430f2a040 docs(log): ceiling-cut entry closes with the green 33/33 gate 2026-10-17 22:51:59 -07:00
Frank fc099d9b3c e2e: accept Plex's session-less hand-off to app.plex.tv
The full-suite re-run after the reset fix came back "1 flaky": 12-crawl reached
plex.franksdank.cloud, the guard cleared, and Plex Web — signed out in the
qa-e2e profile — handed the browser to its own hosted sign-in surface, so the
hostname never settled on the vhost (60 s poll, Expected plex.franksdank.cloud /
Received app.plex.tv). settleThroughGuard now takes a HANDOFFS landing list per
vhost; only plex.franksdank.cloud gets one, every other host still has to land on
its own vhost, and the guard assertions (Anubis cleared, no authentik uidField,
status < 500) are unchanged. Worklog and E2E gotcha 6 carry both symptoms: the
ERR_ABORTED reset and the hand-off.
2026-10-17 22:49:35 -07:00
Frank d2587e1d29 e2e: crawl reset survives app navigation; dragoon docs carry the cut ledger
Two faces of the same verification round.

12-crawl returned to the homepage between links with a bare page.goto, and Plex
Web (signed out, so it renders its own sign-in iframe for its Plex-account flow)
keeps redirecting the top frame: Chromium cancelled our goto with
net::ERR_ABORTED, outside the try, so the whole crawl died — once as a 420 s
timeout in the deploy gate, once at 5.4 s on the same link. backHome() tolerates
that one error class and re-issues; any other error still propagates, and the
per-app verdict (guard settled, hostname, no authentik uidField, status < 500)
is unchanged. Plex was healthy throughout (container up 2 days, 32400 answering
HTTPS 401 / plain-HTTP empty reply), so this was app behaviour, not a fault —
recorded as gotcha 6 on the E2E page.

The 200,704 boot printed 5.5 GiB reserved and 74.1 GiB in all (not the ~5.4/~74
my arithmetic predicted), so every prose figure is re-keyed to the ledger, and
the runbook gains the second production-boot section: marker revision, pool
reservation, shape line, prompt-cache entry size, and the refusal contract at the
new ceiling (400 in 0.86 s naming 200704 and the 96,405-token overage, before any
prefill). Cost stated (two maximum-length conversations never coexist; the 180k
compaction target is unreachable inside a 155,648 window) and purchase stated
(1.7 GiB of page cache, 9.1 GiB against the 2026-10-06 shape).
2026-10-17 22:45:34 -07:00
Frank 7c4dbebe24 frank-dragoon: lower the context to 200704 (ctx and pool together)
Frank's call the same evening as the native-payload cutover: HALOGEN_CTX and
HALOGEN_KV_POOL_POSITIONS both 262144 -> 200704 (a multiple of 4096, the rung
frank-zealot already runs). The checkpoint's native window is 262,144 and the
engine serves anything below it, so this is a RAM decision, not a capability
limit: the KV reservation drops 7.2 GiB -> ~5.4 (the 2026-10-06 pool measured
14.6), and by the FLAGS.md law -- RAM the pool takes is RAM that n-gram table
loses -- the freed positions become page cache for the 47.68 GiB table that
long prompts read. Engine-side total ~74 GiB instead of 75.8.

The 400-zone law is invariant to the value, so the harness moves WITH the
ceiling: models.yml contextWindow 217088 -> 155648 = ceiling - 45056, keeping
the gap that absorbs the chars/4 under-count (worst-case request ~156.2k vs
the 200,704 ceiling, ~44k margin). Compaction now fires at 85% = 132,300 est
(~136k measured); the 180k target of the 262,144 era is unreachable because
the window itself is 155,648. maxTokens 45056 and the coupled
cap/timeout 65536/7200 are untouched.

Cost stated plainly: with pool == ctx, ONE maximum-length request fills the
pool, so two long conversations never coexist (two of <=100k do). Acceptable
because frank-dragoon carries only the slow role and the security-reviewer
subagent, not the interactive workhorse.

Seam re-keyed: the fit-law test now proves the invariant at the new pair, and
the pool law (ctx <= pool <= slots * ctx) still holds with pool == ctx.
2026-10-17 22:29:22 -07:00
Frank 860386d283 docs(frank-dragoon): production boot ledger, worklog, retired-era ctx fix
Deploy verification for fcd7630 (22:20): healthy in ~12 s, e2e 33/33, and the
boot ledger settles the shape — 62.9 GiB weights locked (0.8 vision) + 7.2 GiB
KV in ONE 262,144-position pool (6,656 MiB, two slots) + 5.7 GiB working =
75.8 GiB in all, n-gram 47.68 GiB read from disk, prompt cache ON (12 entries),
MTP depth-2, no clamp line. Live probes: 27*59 -> 1593, tool call, red PNG ->
Red. The two KV figures are reconciled (engine preflight FORMULA 7.4 GiB
includes block scratch + per-slot O(1) state; the RESERVATION is 7.2).

Also corrects two stale claims: the retired swift15 rows named 200,192 ctx
(raised to 262,144 on 2026-10-07 inside that same era), and the OpenWebUI
frank-dragoon row's description is now re-keyed in the live DB (backup
webui.db.bak-20261017b).
2026-10-17 22:23:59 -07:00
Frank fcd763086f homepage: point the frank-dragoon card at the native v2 payload 2026-10-17 22:18:00 -07:00
Frank 13162e6615 docs(frank-dragoon): native-era runbook; retire the swift15 page
New LLM/frank-dragoon-flash-server-v2-native.md carries the shape with the
three deliberate deviations from the card (served id, SPEC_ADAPT=0, engine
pin 0.16.2), the memory ledger with the PIN_TRUNK rejection, the full
measured A/B table, and the companion law (hardlinked stock peonist files,
engine-tool certified). The swift15 page is re-banned RETIRED 2026-10-06 →
10-17 with its rollback terms; both indexes and AGENTS.md (topology table,
service section, runbook index) are re-keyed, and the root index also stops
describing the halogen-27B page as ACTIVE.
2026-10-17 22:18:00 -07:00
Frank e36f6a732c frank-dragoon: promote the A/B verification harness into the repo
The three artifacts that produced the native-vs-swift15 verdict, so the next
payload swap starts from instruments instead of ad-hoc scripts:

  census-frank-dragoon.sh — the engine's own checkpoint tools (verify/ppl/niah)
    over BOTH payloads under one knob set; the pool stays 524288 deliberately
    because the ppl pass scores 297,053 ids as two 262,144-token sequences.
  battery-frank-dragoon.py — behavior battery against the live service: refusal
    census, thinking-token census, tool-call turns, the red-PNG vision probe,
    needle-in-haystack, decode/prefill/prompt-cache rates; discards the
    post-boot warmup request and records the engine's 400 body (which names the
    exact token overage) instead of dying on it.
  trial-frank-dragoon.sh — the guarded candidate window: fetch gate, verify,
    stop+mask, census, boot through a transient FRANK_DRAGOON_ENV_FILE drop-in,
    battery, trap-guarded restore.
2026-10-17 22:18:00 -07:00
Frank 061e61baa6 frank-dragoon: cut the endpoint over to the native v2 abliterated checkpoint
Quat3rnion/halogen-qwen3.8-flash-next-v2-abliterated is now the NATIVE
2026-10-11 v2 conversion of orcarouter's pinned BF16 (66,687,678,432 B,
LFS sha256 b5bd925f...) — 32 bytes smaller than the writer transplant it
used to ship under the same filename. The n-gram table, vision tower and
tokenizer are the stock peonist companions, hardlinked out of the swift15
payload dir: the release publishes only the main file and no source-matched
pair exists, so the engine's own tools are the referee (niah 18/18, ppl
3.968 with mean KL 0.155 against the swift15 logits, 2026-10-17).

Memory saving, per the card: HALOGEN_KV_POOL_POSITIONS 524288 -> 262144,
one pool behind both slots (boot ledger: 7.2 GiB, where the old pool
measured 14.6). HALOGEN_CTX stays 262144 so the per-request ceiling and the
harness contract (contextWindow 217088) do not move; only two MAXIMUM-length
conversations lose the ability to coexist.

Measured against the swift15 boot on identical traffic: cold prefill
+12.6 %, decode 50.0 vs 46.8 t/s, NIAH 18/18 vs 17/18, PPL +2.3 %, thinking
tokens x1.9 (the finetune's reasoning compression is gone), refusals 0/5 on
both.
2026-10-17 22:18:00 -07:00
Frank 3f5ad507c2 frank-dragoon: fetch script for the native v2 abliterated checkpoint
Quat3rnion/halogen-qwen3.8-flash-next-v2-abliterated @main replaced the file
on 2026-10-11: a complete HIP native-v2 conversion of orcarouter's pinned
BF16 (66,687,678,432 B, sha256 b5bd925f), not the r1/r2 writer transplant
the filename used to carry. The release publishes ONLY the main file, and its
card states that the stock peonist n-gram/vision companions are NOT established
as interchangeable (source-derived table would be 51,200,246,084 B against
peonist's 51,200,246,144 B), so the script hardlinks the verified peonist trio
into the new payload dir — a hardlink is a regular file, so the serve gate's
no-symlink law holds and 47.7 GiB is not copied — and leaves the companion
question to the engine's own verify/ppl/niah tools plus the boot ledger.
2026-10-17 21:02:16 -07:00
Frank 4607604ef7 docs(frank-zealot): carry the 19:44 production boot into the runbook
Left uncommitted by the deploy session: the production decode rate (75.8 t/s,
within 1% of the trial arm), the shipped boot ledger, the live probe census,
and the picker-row law (OpenWebUI meta.description is live DB state a swap
must follow into the database, written without restarting open-webui).
2026-10-17 21:02:16 -07:00
Frank 2def6282b5 docs(frank-archon): rollback re-key after 2026-10-17 disk purge
The Swift1.5 w4dn8 .rad, the GLM-4.7 GGUF, and every derived model cache
were purged from dankserver (trash-staged, emptied with approval; 763 GiB
free). Rollback of the iq4r swap is now an HF re-download of the
byte-exact frank8465 mirror (LFS sha256 09dd9306...). Corrects the w4dn8
runbook header (still said LIVE) and rebuilds both Rollback sections;
log.md carries the purge record.
2026-10-17 21:00:26 -07:00
Frank 986bbc9e36 docs(frank-zealot): cutover deployed — production ledger, live probes, picker row
- production boot reproduced the census (22,664/1,461, fit 21,439 leaving
  2,119 >= 1,872, KV 13,328.00 MiB at n_ctx_seq 200,704, n_slots 2), live
  probes green (pong + reasoning_content, tool_calls, 512px -> Red, 75.8 t/s),
  e2e 33/33
- records the two live-path stragglers the swap had to catch: the serve
  script's retired-payload DEFAULTS and the OpenWebUI model row description
  (live DB state, re-keyed with a backup, no forced restart)
2026-10-17 19:48:48 -07:00
Frank f48b45c1fa frank-zealot: sweep the live path — serve defaults, unit text, picker docs
- serve script: header + every fallback default re-keyed to the Bonsai-2 trio
  (vendor LLAMA_BIN, PQ2_0 MODEL_FILE, Q8_0 MMPROJ) and CACHE_TYPE_*:=q8_0 —
  a defaulted run must land on the shipped shape, never on the retired 9B/f16
  pair (the trial window proved a silent default is how a swap goes wrong)
- unit Description now names the payload + KV dtype that `systemctl status`
  will show during the next incident
- OpenWebUI decoration-row description re-keyed, with the law that the row is
  live DB state a swap must follow into the database
2026-10-17 19:42:13 -07:00
Frank 322ebb1658 docs(AGENTS): fleet topology + llama.cpp laws for the PQ2_0 zealot era
- topology row + zealot section: PrismML vendor build, Bonsai-2 PQ2_0,
  q8_0 KV, measured ledger; runbook index active/retired rows
- new durable laws: custom-tensor-type runtime door (vendor build is forced),
  mmproj outside the engine fit projection, first-request warmup artifact,
  thinking-budget probe traps + load-bearing reasoning_effort fold
2026-10-17 19:41:11 -07:00
Frank 7706e06775 docs(frank-zealot): Prism Bonsai-2 PQ2_0 runbook; indexes and log re-keyed
- new runbook: runtime lock (build 10754 / type 142 door), geometry census,
  four-arm VRAM table (f16 wall, q8_0 deployed, q4_0 rung, 2x262,144),
  measured rate ladder, behavior contract, ops notes
- LLM/index.md + vault root index: new ACTIVE entry, 9B era retired in place
- log.md: the trial+cutover wave record, incl. the env-var-name law
2026-10-17 19:41:11 -07:00
Frank f383038437 homepage: zealot card -> Ternary-Bonsai-2 PQ2_0 (censored base)
- card/docs: prism-ml payload link, PQ2_0 spec, ~77 t/s (measured single
  stream, replacing the 9B's ~95)
- warning re-keyed: zealot is no longer an uncensored endpoint, so the card
  carries the base-alignment notice and points uncensored work at dragoon;
  the test's "common uncensored text" equality (zealot==dragoon) is retired
  with the payload it described — dragoon alone carries the legal warning
2026-10-17 19:40:56 -07:00
Frank 64185d63b9 frank-zealot: cutover to Ternary-Bonsai-2 PQ2_0 with q8_0 KV
- env: Bonsai2 payload pair, CACHE_TYPE_K/V=q8_0 (f16 OOMs at 2x200,704:
  34 KiB vs 64 KiB/token over the 16 full-attention layers), measured boot
  ledger 22,664 used / 1,461 free incl. the 873 MiB mmproj that lands AFTER
  the engine fit check, q4_0 fallback rung, CRACK one-line MODEL_FILE swap
- deployer: runtime/model/mmproj gates re-keyed to the PrismML vendor build
  (the only loader for GGML tensor type 142 + prism.hadamard.* metadata)
- seam: payload trio pinned; KV argv assertions now derived from CACHE_TYPE_*
  in the env file — the wrong-var-name f16 fallback that produced the first
  failed trial window can no longer pass silently
2026-10-17 19:40:56 -07:00
Frank 430c42ab18 frank-zealot: Bonsai2 PQ2_0 candidate fetch script (official LFS shas; CRACK variant documented, unpinned) 2026-10-17 19:40:56 -07:00