Commit Graph
100 Commits
Author SHA1 Message Date
vh ec1c482bd5 deploy(park): stonehenge-park v1.0.0-beta.2 on ana-docker
Operator-directed request from park-dev. Rebuilt from tag v1.0.0-beta.2
(commit 2c258f7) and redeployed; park-data volume preserved (28 items,
9 comments verified present after the recreate).

Build source is now exported per-tag to
~/deploy-src/stonehenge-park-v1.0.0-beta.2 rather than overwriting the
single mirror, so the previous tag's tree stays on the host as a
rollback. The mirror was never a git checkout, so the source comes from
`git archive <tag>` against a box that has the repo -- which also leaves
park-dev's working tree untouched.

Verification, and two things worth writing down:

- HEAD 401s on EVERY route, including /healthz and /. So park-dev's
  suggested check `curl -sI .../ui/assets/favicon.svg` reports a false
  failure. GET is 200 with content-type image/svg+xml; the packaging is
  fine and all nine assets are in the wheel. App-wide and pre-existing,
  not a beta.2 regression -- /healthz predates this release.

- /park/due-count returning 0 is not a data-loss signal; it counts what
  is due now, and /park/due is empty across overdue/today/stale. Items
  survived: GET /park returns all 28. README now says to check that
  instead of the due counters.

Also corrected the stack README, which still told the reader to build on
nh3-docker and verify against 10.100.50.40 -- the host decommissioned for
this stack on 2026-08-13 and the one park-dev explicitly asked us not to
deploy to.
2026-08-17 22:21:09 -07:00
vh c4b2278e7d feat(esh-pve-nas): stage the PVE root migration off the USB DOM
Everything but the reboot. Two rerunnable elway playbooks; the host is
still running from the ext4 root and its boot path is byte-identical to
the last 140 days, because grub-install is deliberately held back to the
cutover window.

Phase 1 (esh-pve-nas-stage-zfs-root.yaml): carve a 512 MB /boot LV out
of the 768 MB swap LV, populate it, rsync the 4.3 GB ext4 root into
nvme/ROOT/pve-1, write the copy's fstab.

Phase 2 (esh-pve-nas-stage-bootloader.yaml): ZFS initramfs, grub.cfg,
explicit pve-zfs-root and pve-ext4-rollback entries with stable ids,
grubenv pinned to the rollback so cutover's grub-reboot is a one-shot.

Three landmines the plan did not predict, all caught by verify steps
asserting effective state rather than by reading the plan:

- The /boot LV had nowhere to live. VG pve had 4 MB free and mounted
  ext4 cannot shrink; freeing space from root needs a rescue boot, which
  costs the one-reboot property. Space came from swap (768M -> 256M).

- The runbook's `zpool set cachefile=... nvme` would have broken the
  NAS. Populating a cachefile flips the host from import-by-scan to
  import-by-cache, so a one-pool cache leaves ssd and tank unimported --
  and CT 103 esh-nas has twelve bind mounts spanning all three pools.
  Set on all three instead, verified in the resulting cache.

- update-grub silently emitted a pool-less root=ZFS=/ROOT/pve-1, which
  boots to an initramfs prompt. Debian's 10_linux builds ${rpool}${bootfs}
  and rpool comes from grub-probe --target=fs_label, which returns empty
  because GRUB's ZFS reader cannot open a pool with encryption,
  large_dnode and zstd_compress -- the same feature set that forced /boot
  to stay ext4. The probe failure is swallowed by `2>/dev/null || true`.
  Fixed with a /etc/default/grub.d drop-in plus explicit menu entries.

The transferable lesson: the original verify grepped for the correct
root= string appearing somewhere in grub.cfg, which passes while every
menu entry is still broken. Assert the effective value, not the presence
of a substring.
2026-08-17 22:11:14 -07:00
vh d3e1cc4a41 docs(lobe-chat): TTS works with zero client-side settings now
infra-ops aliased tts-1, tts-1-hd and gpt-4o-mini-tts onto ext-tts's upstream
and extended the lobe-chat-esh key allow-list 20 -> 23 models, so the manual
"set the TTS model to ext-tts, per browser" step this file described a few
hours ago is obsolete. Lobe's stock three-field payload now returns 200
audio/mpeg — verified from the host with this stack's own .env.

Adds the coupling that the fix introduces: the three new names are independent
LiteLLM DB rows carrying their own copy of the upstream URL, so a future
repoint of ext-tts must move all four or stock clients land on a dead engine
without any error on the gateway side.

Comment/doc only — no functional change, no redeploy.
2026-08-17 21:49:04 -07:00
vh 637ed3bd89 memory: Lobe TTS fixed by aliasing stock OpenAI model names at the gateway
Lobe's TTS had never worked. It sends model:"tts-1" and LiteLLM resolves the
model name before routing, so it 403'd against the scoped key's allow-list and
never reached :8198 -- our belief that an unknown model routes to the gateway
default was true of the gateway and false of the LiteLLM path, which is what
hid it.

Fixed at the gateway rather than the client: tts-1, tts-1-hd and gpt-4o-mini-tts
aliased to the same upstream as ext-tts, and added to the lobe-chat-esh
allow-list. Verified with Lobe's exact payload on Lobe's own key. The obsolete
'one-time human UI pass' follow-up is dropped.

Banks two durable facts: those aliases are independent DB rows that must move if
ext-tts repoints, and the infra-ops key has admin rights for /model/new and
/key/update so this class of work does not need sk-corvid.
2026-08-17 21:47:00 -07:00
vh 356752d99c memory: snapshot — heresy gen seat live, irv-ml1 cleared, homepage repo'd, esh-pve-nas DOM planned 2026-08-17 21:44:29 -07:00
vh 8ddc87c852 docs(esh-pve-nas): record the blocked-patching driver and the upgrade ordering
The operator-visible symptom is that PVE cannot be updated on this box for lack
of room. Measured: 225 packages pending, 161 carrying deb12uN/Debian-Security
bumps including ssh, against esh-pve's 8.4.14 versus this host's 8.4.11 and 20
weeks of uptime.

Records the ordering explicitly -- migrate first, upgrade after. The pending set
includes proxmox-kernel-6.8.12-42-pve-signed, roughly 250 MB of kernel plus
initramfs landing in /boot which is on root with 1.3 GB free. Unpacking 225
packages including dpkg and perl into that headroom risks filling the disk
mid-transaction and wedging dpkg on a hypervisor running five guests.

Notes the apt archive-dir redirect as a partial escape hatch if patching cannot
wait, and that zfs-initramfs 2.2.8 is fully capable of root-on-ZFS so there is
no need to upgrade ZFS before migrating.
2026-08-17 21:40:24 -07:00
vh 3e311756d7 docs(esh-pve-nas): split boot from root instead of reinstalling
Operator's proposal, and it is strictly better than the reinstall plan.

Boot and root do not have to share a device. Keep the ESP and /boot on the DOM
as ext4 -- so GRUB never has to read ZFS, which matters because the nvme pool
has encryption, large_dnode and zstd_compress enabled and GRUB cannot read
those -- and move root to nvme/ROOT/pve-1. The initramfs imports the pool and
pivots.

What this buys over the reinstall: the nvme pool survives, so no guest
migration, no export/import of ssd and tank, no reinstall. Downtime is one
reboot rather than half a day. Rollback is a GRUB menu entry, because the ext4
root stays on the DOM untouched. And it retires the actual top risk -- with
root on NVMe, a USB bus reset mid-run no longer takes the running system down;
the DOM becomes read-mostly, written only on kernel updates.

Preconditions verified and already met: UEFI with grub-efi, zfs-initramfs
2.2.8-pve1 installed with 76 ZFS files already in the running initrd, root only
4.3 GB to copy, swap negligible against 125 GB RAM.

Two traps recorded: canmount=noauto on the root dataset or ZFS tries to mount
over the running root; and cachefile is currently none with a 0-byte
zpool.cache, so the pool imports by scan today and must be given a cachefile
before the initramfs is rebuilt.

The reinstall plan is retained as the fallback.
2026-08-17 21:36:30 -07:00
vh 2275e11be0 docs(esh-pve-nas): plan the migration off the USB DOM; flag the NFS blast radius
PVE root on esh-pve-nas is a USB Disk-on-Module: 6 GB ext4 with the host's only
ESP. A DOM is SLC/pSLC so wear is not the driver -- the problems are that it is
on the USB bus (a reset drops root under a running hypervisor), has no headroom,
and is unmirrored while 928 GB of mirrored NVMe sits 96% empty.

Runbook targets a fresh PVE install to ZFS RAID1 across both NVMes. In-place
conversion is unsupported, and adding an ESP to the existing NVMes is impossible
-- both are whole-disk ZFS members with 1.7 MiB free and proxmox-boot-tool
manages nothing today.

The headline risk is not on the host being rebuilt: CT 103 esh-nas IS the NAS
at 10.0.50.50, and both esh-docker-vm and esh-pve mount it hard. Taking this box
down stalls esh-pve's storage layer and wedges esh-docker-vm into the D-state
whose only remedy is a host reboot -- the incident shape already on record.
Quiescing those clients is step one of the window, and the README now warns
against casual reboots.

Config snapshot captured off-box to nh3-dev (0600) with /etc/pve, network and
fstab config plus zpool/zfs/disk-by-id/guest state; the newest on-disk copy
before this was June 2024.
2026-08-17 21:32:33 -07:00
vh ca8c0a318e docs(lobe-chat): correct the TTS notes — the deploy's TTS never worked
Two claims in this stack's docs were reasoned from the wrong hop, and one of
them hid a dead feature since deploy. Re-measured from esh-docker-vm against
the live .env:

1. "An unknown `model` routes to the gateway default" — true of :8198, false of
   the path Lobe takes. LiteLLM resolves the model name first, so Lobe's default
   `tts-1` returns 403 (`key not allowed to access model`) and never reaches the
   gateway. `ext-tts` returns 200 + audio. The endpoint inheriting
   OPENAI_PROXY_URL is necessary but not sufficient: Settings -> TTS -> OpenAI
   TTS model -> `ext-tts` is a required one-time step per browser, and removing
   it needs a LiteLLM alias plus a key allow-list entry (both master-key, so
   infra-ops).

2. "`response_format: mp3` ... set it in the UI" — not possible. Lobe's OpenAI
   TTS client sends `{input, model, voice}` and nothing else (server bundle
   chunks/29685.js), so format is not selectable from this stack at any level.
   The deploy gets the fleet gateway's default (WAV, ~23.5 MB for a 245 s turn),
   relabelled `audio/mpeg` by LiteLLM. That is tts-dev's fence, not this one's.

Comment/doc only — no functional change, so the host copy needs no redeploy.
2026-08-17 21:28:59 -07:00
vh d1f4f1cb96 docs(homepage): record the ALLOWED_HOSTS fix, .env lockdown, and verification method
HOMEPAGE_ALLOWED_HOSTS now carries the IP:port form; direct access to
http://10.0.50.45:5100/ returns 200 and the host-validation errors are gone
from the container log.

The .env was mode 644 holding the Plex and Jellyfin API keys; now 600. It is
root-owned, so editing it needs the infra-ops identity -- lkraven has only
password-sudo on that host.

Also records that Homepage renders client-side, so grepping the served HTML
to verify a config change is the wrong instrument (it gave a stale prerender
and then an empty page). GET /api/services is the honest check, and config
changes need a recreate rather than a restart.
2026-08-17 21:14:22 -07:00
vh c5beeac32d feat(homepage): bring the fleet dashboard under version control
Homepage on esh-docker-vm:5100 was the one stack whose config lived only on
the host, edited in place. Its version history was six hand-rolled
services.yaml.bak-* files. Now canonical here and deployed with
deploy-stack.sh like everything else; the .bak files are gone.

Corrections from the audit:
- ANA-Firewall described a 'Fortigate 81F'. It is a FortiGate-80F running
  FortiOS 7.2.10, verified live against the device.
- NH3-Ansible pointed at 10.100.50.42 as an 'Ansible control node'. That host
  is nh3-extdev, the manager/external-dev successor after nh3-ansible was
  retired. Renamed and re-described.
- Dropped the UltraSeedbox layout group: nothing provides it, so it only ever
  rendered empty.

Adds .env.example and a README documenting the two-path service model (docker
label discovery across five engines vs manual entries), the labels-only-apply-
on-recreate rule, and the foot-guns found: HOMEPAGE_ALLOWED_HOSTS matches
host AND port so a bare IP does not cover IP:port; :2375 is plaintext and
unauthenticated on all five engines; ping: cards can only be judged from the
dashboard host.

Verified after deploy via /api/services: 105 cards across 19 groups, both
corrections live, ana-docker discovery intact.
2026-08-17 21:10:37 -07:00
vh 4b6daadb16 memory: operator confirms the heresy gen seat working well in real use
Records the one signal the synthetic gates cannot provide -- multi-turn
degeneration is stochastic and invisible to probes, and four synthetic tests
once validated three non-fixes on this exact seat.

Not yet the 60k-token bar the prior seat cleared, so the rollback weights
stay in place.
2026-08-17 20:19:18 -07:00
vh d676a1375b memory: watch DavidAU's heretic Qwen3.8, not Cold-Fusion-GAIN V1.1
Cold-Fusion-GAIN V1.1 examined and not adopted -- it is a capability
finetune of stock Qwen3.8 and every bench row is labelled [non heretic], so
adopting it would reintroduce base refusals the current seat does not have.

Records why it reads as uncensored at a glance: DavidAU's back catalog is
almost entirely Uncensored-Heretic builds, so the naming pattern implies it.
The heretic stage for this one is still in progress from base, and that is
the release worth watching.

Also banks what makes it interesting when the heretic build lands -- real
third-party benchmark gains over stock, claimed MTP acceptance well above
ours, thinking tokens cut to a fraction -- and the two caveats: the MTP
numbers are GGUF/llama.cpp not vLLM, and a trained MTP head means the free
CPU-hash gate would not apply.

Also drops the now-stale 'primary until the DavidAU Qwen3.8 lands' clause
from the superseded seat entry.
2026-08-17 19:26:15 -07:00
vh 11b688ff68 feat(gen-seat): promote absolute-heresy to the live gen seat
MuXodious/Qwen3.8-27B-absolute-heresy (Heretic v1.4.0 + SOMPOA, trial T377,
pin c2374593) quantized through our mixed NVFP4+FP8 recipe and promoted after
passing the full gate on the probe port.

Gate vs incumbent -- MTP acceptance 47.2% (48.2%), decode 103.5 tok/s (96.4),
prefill 6618/5403 at 6.7k/27k (6334/5085), TTFT 27k 5.00s (5.31s), perplexity
6.910 (7.059, 2.1% better), surface 6/6, abliteration compliance 4/4. On our
battery-instruct arm -- the framing that actually elicits refusals -- 0/55 with
zero EMPTY, so no catatonia at the hard edge.

Speed deltas are image-confounded: the probe ran the seat's pinned nightly
while the incumbent's stored numbers came from an earlier image. Read as not
worse. Acceptance, perplexity, surface and refusal are apples-to-apples.

All 7 LiteLLM aliases verified end-to-end. GPU0 at 91.3/97.9 GB with meromero
healthy -- more headroom than the previous build. Incumbent weights untouched
and .env.bak-heresy-20260817 in place for rollback.

Candidate is a 2-day-old RC1 with ~348 downloads; watch real multi-turn use.
2026-08-17 17:23:35 -07:00
vh 993421bf59 fix(post-quant): handle sources that keep mtp.* inside a numbered shard
post_quant assumed the source ships a standalone model-mtp.safetensors, which
is how JonathanColetti's grafted head is packaged. MuXodious/absolute-heresy is
an unmodified full checkpoint, so its mtp.* lives in model-00012-of-00012 --
the copy silently did nothing while the index was still rewritten to point at
model-mtp.safetensors, leaving 15 unresolvable tensors. Tensor counts looked
correct; the checkpoint would have failed at load.

The existing FAILED-CHECKS assertion caught it, which is the design working.
Now extracts from the numbered shard when the standalone file is absent.

Verified on the heresy build: 1968 tensors, all resolvable, 15 mtp, 333 visual,
no missing shards, no orphans.
2026-08-17 17:18:01 -07:00
vh b0c2d3d1c4 fix(bench): serve_probe must mirror the live seat -- image, parsers, context
Three defects, each of which produced a false read on the candidate:

1. Hardcoded vllm/vllm-openai:latest. The Qwen3.8-27B gen seat is pinned to a
   nightly carrying the #51113 qwen3_5_mtp x GDN fix; probing on :latest
   reproduces the multi-turn corruption we already diagnosed and reads as a
   candidate failure. Now PROBE_IMAGE, defaulting to :latest for older seats.

2. --speculative-config JSON died twice on quoting. The inner double quotes are
   stripped by the outer double-quoted ssh string, and then bash BRACE EXPANSION
   splits {"a":1,"b":2} on the comma. Needs escaped quotes AND remote-side
   single quotes; both traps documented inline.

3. No --tool-call-parser/--enable-auto-tool-choice/--reasoning-parser. Without
   them surface_test reported tool calling as a 400 and measured a thinking split
   of reasoning=0ch -- both probe-config artifacts, not model defects. Re-running
   with the seat's flags took the candidate from 5/6 to 6/6.

Also adds PROBE_MAXLEN; the hardcoded 32768 rejected prefill_bench's ~27k prompt.
2026-08-17 17:17:30 -07:00
vh 2c3602869f fix(gen-seat): hash bf16 tensors via uint8 reinterpret, not numpy
numpy has no bfloat16, so .numpy().tobytes() raised
'TypeError: Got unsupported ScalarType BFloat16' on real checkpoints.
Flatten then view(torch.uint8) before hashing.

Result on the heresy candidate: VERDICT IDENTICAL -- all 15 mtp.* tensors
byte-identical to the incumbent's verbatim base graft, the head already
measured at 47.7% acceptance in production. The ~56 GB bf16 acceptance gate
is redundant, so no second seat comes down.
2026-08-17 16:29:45 -07:00
vh 254c588921 feat(gen-seat): CPU-only MTP head check so the gate costs no second seat
Operator ruled the probe port for validation; runbook updated to match.

The bf16 MTP acceptance gate is ~56 GB resident, which on a full 97.9 GB card
means downing meromero-charrp as well as gen -- freeing gen's 0.43 (~42 GB)
alone is not enough. Two seats down to answer one question.

compare_mtp_head.py answers the common case for free. The
Qwen3_5ForConditionalGeneration wrapper never loads the MTP head, so PEFT
merges, Heretic runs, and llm-compressor passes all leave mtp.* as it came
from the base. It hashes a candidate's 15 mtp.* tensors against the
incumbent's grafted-verbatim head -- the one measured at 47.7% acceptance in
production through this exact pipeline. Identical means the acceptance
question is already answered; different means the head was edited and the
real gate is warranted; missing means it was dropped.

CPU only, reads just the shard holding mtp.*. The runbook states the residual
risk plainly: an identical head proves the head is intact, not that the
abliterated body still drafts well with it -- which the Stage-3 acceptance
measurement on the 22 GB quantized build catches anyway.
2026-08-17 16:25:28 -07:00
vh 7997f111b0 docs(gen-seat): runbook for the absolute-heresy swap
Candidate MuXodious/Qwen3.8-27B-absolute-heresy (Heretic v1.4.0 + SOMPOA,
trial T377), pinned c2374593. Beats the incumbent on both axes: refusals
2/101 vs 12/100, first-token KL 0.0759 vs 0.1191. Structurally a clean full
checkpoint (1199 tensors, 15 mtp.*, 333 visual.*, lm_head), so the existing
mixed NVFP4+FP8 recipe applies with no graft-and-reconstruct.

Runbook carries the bf16 MTP-acceptance gate before any quant spend, the
llm-compressor ignore-pruning foot-gun, the three measurement traps
(cache-busting, unseeded prefill nonce, PPL with spec off), the .env 0600
sudo trap, the GPU0 co-tenant starvation risk, and rollback.

Flags that the candidate is a 2-day-old RC1 whose own card carries a broken
GGUF benchmark block (RC1 and RC2 report identical at-chance scores across
three benchmarks), so its numbers are claims rather than measurements.
2026-08-17 16:22:00 -07:00
vh c18f5c5d33 memory: WT #401 closed on demo verify; host ulimit floor staged not active
worldtree-dev closed #401 on our demo verification. Records the two-layer
state (their e41b139 compose pin verified on demo, covered-not-verified on
personal/pinned; our daemon floor staged), the measured fact that
default-ulimits is not SIGHUP-reloadable on Docker 29.4.3, the explicit
no-dockerd-restart decision, live-restore parked as a separate call, and
the one ping we still owe once worldtree-personal recreates.
2026-08-17 16:04:08 -07:00
vh 7f3f265384 feat(corviduo-dev): stage a host-wide docker nofile floor (65536) for WT #401
Worldtree #401: a slow fd accrual in worldtree-personal hit the 1024 soft
nofile ceiling and converted into a hard deadlock. Operator authorized the
raise 2026-08-17 (relayed via worldtree-dev); sizing 65536 agreed.

Applied at the daemon layer rather than compose because /opt/worldtree-*/
compose.yaml on corviduo-dev is written by the team CI deploy identity -- a
host-side compose edit reverts on the next deploy and would leave a false
'raised' record. Daemon config is infra-ops-owned and covers all 13
containers on the box. worldtree-dev shipped a redundant compose-level pin
(e41b139) as the belt to this braces.

daemon.json is written and valid, but the floor is STAGED, NOT ACTIVE:
default-ulimits is not in dockerd's SIGHUP-reloadable set. Measured on
29.4.3 -- the post-reload 'Reloaded configuration' log enumerates the live
config without default-ulimits, and a fresh container still reports
ulimit -n 1024. Activation needs a full dockerd restart, which bounces every
container; not taken, since #401 is not urgent at fd ~100 and the compose
pin already covers worldtree. The playbook documents this and its verify
step 3 fails by design until a restart happens.
2026-08-17 15:58:00 -07:00
vh 2686042106 memory: fleet IPv6 state + verified VPN topology; ana-wg key material locked down
Durable capture ahead of the ESH fiber install (2026-08-18) that puts the
house behind CGNAT and breaks Site Magic on IPv4 -- IPv6 becomes the
escape hatch and the likely first consumer of fleet v6.

Topology verified rather than assumed: Site Magic between UniFi units,
IPsec IKEv2 colo<->UniFi, and WireGuard as a remote-access convention
only, host-based on ana-wg behind a FortiGate UDP VIP. The FortiGate
port-forwards and never terminates WireGuard, so FortiOS 7.2's lack of
native WG is a non-issue.

IPv6 today: NH3 WAN live, colo and ESH none. AT&T delegates exactly one
/64 at NH3 -- established by forcing the prefix ID from auto to 0 and
observing the subnet not move, since the c110/c11f pattern otherwise
reads as a /60. PD enabled on nh3-iot to measure, then reverted; all
five NH3 LANs are back to ipv6_interface_type=none.

Also fixed on ana-wg: wg0.conf, keys/*_priv, keys/*_psk and the client
configs were mode 644 with private key material in them. Now 600, with
keys/ and configs/ at 700. wg-quick@wg0 stayed active, three peers
intact.

Corrects two stale in-flight rows: the DS regeneration is retired, not
queued, and SPEC-ds-regeneration.md is deleted rather than untracked.
2026-08-17 13:50:34 -07:00
vh 9c1405b1f9 docs(refusal-probe): retire the DS v1 regeneration — unqueued, do not run
brokkr-smithy-dev withdrew the request on the operator's call
(msg 01M088G7NQ5G42ES30YPJV4Y3V). Two reasons: the ictrl-pair-unwrapped /
ictrl-pair-wrapped control isolates the classifier over-fire cleanly,
where DS's cross-class delta only bounded it; and DS v2 is releasing
soon, so a k=5 v1 baseline baselines a superseded version.

The spec stays banked as the record of the run that will not happen --
axes, per-class grading asymmetry, and run parameters remain correct.
Checklist struck through. The staged probe.py was pulled from /mnt/smithy
when the request was withdrawn; absence verified from nh3-dev, so the
path recorded in ec0b6e5 no longer resolves.
2026-08-17 09:23:24 -07:00
vh ec0b6e5e71 docs(refusal-probe): record brokkr's staged probe path + sha, and the all-16-axes caveat
brokkr-smithy-dev staged refusal-map-probe.py on /mnt/smithy rather than
leaving it as a run-time ask (msg 01M082P4YPJTDJCF33BEHNYW0M). Path and
full sha256 recorded and verified present from nh3-dev.

The probe has no per-axis selection flag: it runs all 16 axes regardless
of MAP_LIGHT, so the DS run yields a 16-axis artifact, not the 8 dropped
ones. Kept as-is — the creative-half rerun is a free within-model
consistency check against the 50 samples already collected.
2026-08-17 07:41:54 -07:00
vh 0b32b112bd memory: snapshot for /clear — gen-seat saga closed, Lobe live, litellm upgraded
Refreshed Current state (dropped superseded gen-seat history now covered by
the RESOLVED entry + playbook 3.8; added Lobe Chat, litellm upgrade+cap,
updated follow-ups). Logged 4 new Recent decisions (gen-seat two-cause
resolution + the synthetic-probe-validated-3-non-fixes meta-lesson, Lobe
stand-up, litellm upgrade, abliteration-catatonia). Handoff at
/tmp/infra-ops-handoff.md. Index 284 lines, no archival.
2026-08-17 02:23:59 -07:00
vh 2185964a6a memory: gen seat RESOLVED — mixed FP8-attn build on pinned nightly, coherent through 60k
Supersedes the stale 'GEN SEAT = AEON' current-state line. Records the
final resolved config (JonathanColetti/Heretic mixed NVFP4+FP8 on pinned
vLLM nightly, MTP on), the two-real-causes root cause, the AEON purge, and
the pinned-nightly follow-up (move to stable once #51113 ships). A cold
session now reads the correct gen-seat state.
2026-08-17 02:13:42 -07:00
vh 2f2bbce73d docs(quant): correct 3.8 — TWO real causes, not a lone defective quant
Operator correction to the prior 3.8 framing (d28a371), which over-blamed
AEON and dismissed the vLLM bug as a mere amplifier. Both were real and
compounded:

- Cause 1 (real, upstream): the qwen3_5_mtp x GDN partial-accept bug
  (#51113), architectural across vLLM/SGLang/llama.cpp, genuinely improved
  by the nightly fix -- not just an amplifier.
- Cause 2 (real, quant): AEON is FULL W4A4 (A4 activations on attention),
  the bottom of the KNOWN activation-precision gradient already in 1
  (W4A4 < W4+FP8 < W4+bf16) -- mildly subpar, not 'defective'. On top of
  Cause 1 it degenerated ~15-20% of real multi-turn generations.

The mixed FP8-attention build sits a rung up that gradient and is coherent;
a W4+bf16 build would be higher still at a prefill cost. Process lessons
retained (two causes mask each other; stochastic degeneration is invisible
to n=1 probes; isolate weights in parallel with serving flags -- but the
weight swap alone would NOT have found the real vLLM bug).
2026-08-17 01:25:37 -07:00
vh d28a371049 fix(gen-seat): AEON W4A4 was the defect — purged; mixed FP8-attn build is primary gen
Root cause of the multi-day degeneration hunt, operator-confirmed: the AEON
NVFP4 W4A4 quant (sakamakismile/Qwen3.8-27B-AEON-ULTIMATE-UNCENSORED-NVFP4,
full W4A4 incl. attention) went degenerate ~15-20% of generations in real
multi-turn use and forced regenerates. MTP, prefix-caching, and the gateway
all merely AMPLIFIED it, which is why MTP-off, APC-off, and the vLLM #51113
fix each 'helped' a synthetic probe without fixing it -- three plausible
false root-causes, each passing one clean run then failing in real use.

The fix was the WEIGHTS: the in-house JonathanColetti/Heretic mixed
NVFP4+FP8 build (qwen38-27b-uncensored-nvfp4-mixed, FP8 attention not W4A4,
same base, same MTP) is coherent through long multi-turn with MTP ON. W4A4
*attention* was the defect; FP8 attention is not.

This commit:
- GEN_MODEL -> the mixed FP8-attn build (primary gen until DavidAU 3.8 lands)
- GEN_IMAGE pinned to vllm/vllm-openai:nightly-311b3513... (v0.27.2rc1.dev150,
  carries #51113; pinned by sha so it does not drift on the next pull)
- AEON weights PURGED from /tank (no-good), safety-checked not-in-use first
- playbook 3.8: the stochastic-W4A4-degeneration lesson + isolate-weights-early
  + do-not-declare-a-fix-from-one-probe (it validated three non-fixes)

AEON is re-pullable from HF if ever needed, but the operator ruled it no-good.
2026-08-17 00:59:50 -07:00
vh 1f5b2cbcb0 revert(gen-seat): back to MTP-off known-good — APC-off mitigation failed in real use
The MTP-on + prefix-caching-off mitigation (63a3cb2) passed synthetic
7-turn probes but the operator still saw severe degeneration in real use.
A passing synthetic probe is NOT sufficient evidence -- it under-covers
real workloads (content distribution, conversation depth). Reverted to the
verified known-good: MTP off, prefix caching on (the 7bd38b3 state), ~half
decode speed but coherent. Operator is driving it to re-confirm.

Lesson reinforced (the recurring one this session): do not trust a
synthetic reproduction to VALIDATE a fix for a bug that only manifests in
the operator's real usage -- it validated a non-fix twice tonight.
2026-08-16 23:05:20 -07:00
vh 63a3cb2d86 fix(gen-seat): MTP mitigation — disable prefix caching, keep MTP (speed restored, multi-turn clean)
The qwen3_5_mtp corruption (playbook 3.7) is gated on MTP x prefix-caching
TOGETHER (vllm#43559 / #47194), per both cross-frontier peers. Disabling
prefix caching (--no-enable-prefix-caching; vLLM V1 defaults it ON, so the
explicit --no- form is required) forces the GDN cache into a mode where the
partial-accept align-path bug is inert, so MTP can stay on.

Verified on our stack (AEON W4A4): MTP on + prefix-caching off -> the 7-turn
varied series stays coherent through 3.9k tokens, zero cross-turn bleed, at
104.6 tok/s / 53.6% acceptance -- the FULL MTP speedup restored (vs ~half
with MTP off), losing only prefix-cache reuse. All 7 aliases route.

Ruled out on the way: num_speculative_tokens=1 (corruption is
depth-independent, n=1 and n=2 both corrupt); switching to SGLang (vLLM /
SGLang / llama.cpp mainline all share the architectural GDN-rollback bug).
Proper upstream fix (#51113) is in main / v0.27.2rc0 only, not stable, so we
hold at APC-off rather than jump the fleet gateway to an RC.

Supersedes the MTP-off config from 7bd38b3.
2026-08-16 22:44:14 -07:00
vh a8ed6e7428 docs(quant): record the MTP-corrupts-Qwen3.8-multi-turn lesson (playbook 3.7)
The single hardest bug of the night, and invisible to the existing
acceptance gate: a LOADED, healthy-accepting MTP head still corrupts
Qwen3.8-27B multi-turn output past ~2k cumulative tokens (length collapse +
cross-turn content bleed), while single-turn is perfect. Model-independent
across all three of our Qwen3.8 quants; Qwen3.6 on the same qwen3_5_mtp
method is clean; disabling MTP fixes it. New rule: gate MTP on a multi-turn
coherence probe, not just single-shot acceptance.
2026-08-16 22:24:19 -07:00
vh 7bd38b33b5 fix(gen-seat): disable MTP — qwen3_5_mtp corrupts Qwen3.8-27B multi-turn past ~2k tokens
Root cause of the long-hunted 'gen goes degenerate in conversation',
isolated 2026-08-16 and operator-confirmed. qwen3_5_mtp speculative
decoding corrupts Qwen3.8-27B output once cumulative multi-turn context
passes ~2,000 tokens: the draft head's bad tokens get accepted and the
reply degenerates into CONTEXT-BLEEDING (a 'describe durian' answer that
contained the Krebs-cycle and winter replies from earlier turns), then
collapses to a few words.

Isolation, each step measured on the varied 7-turn probe:
  - not the gateway (identical input -> gateway == direct; echo intact)
  - not presence_penalty (1.5/0.5/0.0 all collapse), not temperature
    (1.0 collapses harder), not repetition (varied unrelated topics
    collapse identically -> it is context length, not template-lock)
  - model-INDEPENDENT across all three Qwen3.8-27B quants we serve
    (AEON W4A4, unsloth FP8-attn, in-house mixed)
  - Qwen3.6 (char-rp-reasoning) and Gemma-4 (char-rp) are CLEAN
  - DECISIVE: same Qwen3.8 model + same conversation, MTP OFF -> coherent
    through 4k+ tokens, no bleed. MTP is the cause.

Qwen3.6 runs the same qwen3_5_mtp method and is clean, so the 3.6 MTP
head/graft is fine and the 3.8 one is not (suspects: the bf16 MTP graft,
or spec depth 3). COST: ~half decode tok/s without spec decoding.
Accepted as known-good until the 3.8 MTP is fixed; first thing to try on
re-enable is num_speculative_tokens=1. Seat restored to AEON W4A4 (the
production choice); verified clean on the varied series after this change.
2026-08-16 22:22:58 -07:00
vh 01b5ad93ed chore(litellm): upgrade v1.91.0 -> v1.97.0; purge + cap the 6GB spend-log DB
Operator: update LiteLLM to latest and repull; get rid of the spend-log
DB and cap its growth.

Upgrade: pinned v1.97.0 (latest stable point release; v1.98.0-rc.1 skipped
as a pre-release on the fleet gateway, v1.97.0-stable not yet cut). Image
pre-pulled, DB pg_dump'd (1.8GB gz, keys+config+schema) and .env backed up
before the Prisma migration, which applied cleanly.

DB was 6.08 GB, 6.02 GB of it LiteLLM_SpendLogs storing full prompt+
completion bodies (store_prompts_in_spend_logs: true). Purged via TRUNCATE
on the running 1.91 BEFORE the upgrade so the schema migration ran against
an empty table -- 6081 MB -> 16 MB, keys (32) and models (3) intact.
'Get rid of the db' read as the spend-log DATA, not the database: dropping
it would have destroyed every virtual key (incl. the Lobe key) and the
model config in the same DB.

Cap: store_prompts_in_spend_logs -> false (bodies no longer persisted;
lightweight cost/usage rows and cross-project spend tracking survive) plus
maximum_spend_logs_retention_period 7d / interval 1d as a hard age bound.

Verified post-upgrade: v1.97.0 running, liveliness 200, 31-model roster,
chat round-trip on master + scoped Lobe key, key scoping still enforced
(glm-5.2 blocked), ext-tts 200, and store_prompts confirmed off (a marked
prompt persisted 0 bodies; 4 lightweight rows). Rollback: .env
LITELLM_TAG=v1.91.0 + the 1.8GB dump, both on the host.
2026-08-16 21:23:07 -07:00
vh 163a7252ec fix(lobe-chat): repoint the System Agent off its gpt-5-mini default onto fleet models
The gpt-5-mini calls were Lobe's System Agent -- a background model,
separate from the chat model, used for auto-naming conversations, history
summarization, translation, query rewrite, thread naming, and assistant
metadata. Its default is openai/gpt-5-mini, which our OpenAI provider (the
gateway) forwards verbatim; the scoped key blocks it, so every background
task 403'd and the log filled with 'Tried to access gpt-5-mini' while
auto-naming silently failed.

Set SYSTEM_AGENT to route all six documented keys (topic, translation,
agentMeta, queryRewrite, historyCompress, thread) at fleet models --
summarizer for the naming/summarize tasks (same seat as gen at temp 0),
gen where quality matters. Any key left unset falls back to the gpt-5-mini
default, so all six are explicit.

Notably this one IS env-configurable (SYSTEM_AGENT), unlike the per-model
output-token cap which is UI-only -- a mixed result on the
manageable-by-agent axis.
2026-08-16 21:02:43 -07:00
vh cac75cbffb docs(lobe-chat): resolve the TTS env-vs-UI question — it is a split
Verified against the running image, not docs. TTS goes browser -> Lobe's
server route (backend)/webapi/tts/openai -> the OpenAI provider, whose
server base URL is OPENAI_PROXY_URL, so the endpoint inherits the gateway
and reaches ext-tts with no extra config (route probes 401, i.e. exists).
But there are ZERO process.env.*TTS*/*AUDIO*/*SPEECH* vars: voice, model,
response_format and enable live in a client-side store (bundle key
TTS_SETTING_KEY='tts'), UI-configured per browser.

So against the manageable-or-scriptable criterion: the load-bearing part
(endpoint) is env-scriptable and wired; the rest is a one-time UI setup,
not a maintenance surface. response_format=mp3 is the one thing not
env-forceable -- verify at first use or eat 23.5MB/turn WAV.
2026-08-16 16:56:30 -07:00
vh 933253d42e docs(lobe-chat): voice surface fully safe — ballad/verse aliased by tts-dev
tts-dev aliased the last two unaliased OpenAI voice names (ballad->emmie,
verse->donut, tts-stack c55bc3c). Verified: the full modern OpenAI voice set
returns 200, only a genuinely-unknown string 404s. A stock Lobe picker can
no longer produce a 404 or trip the router cooldown, so the voice string no
longer needs pinning; still pin mp3 for response size.
2026-08-16 16:54:32 -07:00
vh 25fa18efb8 docs: correct stale TTS voice warning (tts-dev); record DS regeneration spec (brokkr)
tts-dev answered the Lobe onboarding, live-verified. Corrects a warning I
shipped in the lobe-chat stack: the OpenAI voice names are ALIASED not
rejected (echo/alloy/onyx/ash->donut, nova->miranda, shimmer/coral->emmie,
fable/sage->glados), so a UI voice mis-click is not the hazard I recorded.
Only ballad and verse 404. The old 71.2s per-call cap is dead (Zonos-era);
dots chunks server-side and renders a 592-word call intact. Real constraint
is size (245s WAV = 23.5MB -> request mp3) and that the seat SERIALIZES
generation, so sustained Lobe volume is a real capacity question to report
to tts-dev.

Also records brokkr's DS regeneration spec verbatim from his probe source
(msg 01M06FN7EE29M8YWP0GK517V4B): the 8 dropped axes (5 operational + 3
meta), the BLUEHERON meta system prompt, and the per-class framing that a
label-level rebuild would lose -- operational uses system=None and an
18-CHARACTER refuse floor at max_tokens 45, meta scores a separate
BLUEHERON leak count that must not collapse into the refuse rate, both
distinct from the creative class's word floor. Queued, gated on the GPU1
window; no deadline (weights not scheduled for reuse). Recorded so it is
run from the artifact, never reconstructed from labels.
2026-08-16 16:51:40 -07:00
vh aba7cda33e fix(refusal-probe): retract an invalid cross-battery comparison; adopt the two-control pair
RETRACTION. I claimed my arm-3 92.5% 'exceeded' brokkr-smithy-dev's 62.5%.
That comparison was invalid. His diff against his own artifact shows my
battery-instruct.yaml reproduces only his CREATIVE class -- 8 of 16 axes --
dropping all 5 operational (violence/incite, crime/fraud, cyber/malware,
selfharm/methods, privacy/stalk) and all 3 meta (meta/sysprompt,
meta/ignore, meta/dan), while adding 2 controls he never had, at k=5
against his k=2. His figure pools 16 axes; mine is creative-only. Different
denominators, so there was never a delta to report.

Cause: I rebuilt his instrument from his MESSAGE, and the class field lives
in the artifact, not the prose. Lesson recorded -- reconstructing a peer's
instrument from their description reproduces what they described, not what
they ran; diff against the artifact before claiming comparability.

Adopts his amendment to my control fix, which is better than my proposal. I
argued a single UNWRAPPED control suffices since over-fire is a property of
the scorer, not the wrapper. True but incomplete: one control gives only a
scorer FPR baseline and cannot attribute anything to the wrapper. Two -- the
same benign scenario unwrapped AND wrapped -- make the wrapper the only
variable, so the delta between them is the wrapper's contribution to
classifier firing. Costs one prompt.

ictrl-reunion stays contaminated and unchanged, now with an explicit
do-not-fix marker: rewriting it would break comparability with results
already collected, and it is the cleanest live demonstration of the very
confound this battery measures.
2026-08-16 16:45:17 -07:00
vh e9362de065 feat(lobe-chat): stand up Lobe Chat on esh-docker-vm over the LiteLLM gateway
Replacement candidate for the hand-rolled gateway-chat HTML surface, which
the operator does not want to keep improving -- it has already produced two
defects tonight.

Chosen over Open WebUI on weight, measured from the registries rather than
recalled: Lobe 143 MB compressed / 1 layer vs Open WebUI 1,825 MB / 19
layers, a 12.8x difference. Open WebUI was declined in June 2026 on weight
and that still holds; its secondary recorded objection (empty-tools 400
against vLLM) is now moot since strip_empty_tools covers the normal API
path and only missed LiteLLM's built-in playground.

CREDENTIAL POSTURE: deliberately NOT the shared all-agents key, which
reaches the paid GLM/Kimi passthroughs -- a LAN-exposed chat UI holding it
would let anyone reaching the port spend vendor credits from a pool shared
across every project. Minted a scoped LiteLLM virtual key
(key_alias lobe-chat-esh) limited to the 20 free local models, and verified
the scoping BOTH ways: gen answers, glm-5.2 / kimi-k3 / gen-frontier all
return 'key not allowed to access model'. Secrets vaulted, host .env 0600.

Verified from INSIDE the container, not just from the host: /v1/models
returns the fleet seats and a gen round-trip returns 'ok', so the app's own
network path and key both work. Container healthy, / -> 307 -> /chat -> 200.

Documents the open question this deploy exists to answer: whether Lobe's
TTS is ENV-configurable or UI-only. That is the operator's deciding
criterion and is NOT yet established -- Open WebUI has dedicated AUDIO_TTS_*
vars, Lobe documents a shared OPENAI_PROXY_URL which should carry TTS since
LiteLLM serves audio/speech on the same base, but that is inference.

Also records the ext-tts voice foot-gun: unknown voices 404 and can trip
the router cooldown, so the voice must be pinned rather than left at a UI
default.
2026-08-16 16:42:05 -07:00
vh 766c65801c memory: AEON accepted on the gen seat; canonical sampling; four-wrong-diagnoses lesson
Gen seat state, the abliteration catatonia signature, canonical Qwen3.8
sampling with the wrong-mode presence_penalty fix, the two real
gateway-chat defects, and the single-file bind-mount inode trap.

Also records the methodology failure honestly: four disproved hypotheses
on one bug, caused by a harness that varied the QUESTION along with the
conversation depth, so a narrower question drawing a shorter answer read
as degeneration. Banked as rules -- hold the final question fixed when
comparing across depth, do not infer trends from n=3 when identical
inputs span 25-465 words, and ask for the real failing transcript before
building a synthetic reproduction.
2026-08-16 16:31:24 -07:00
vh 3462b5336c config(litellm): apply canonical Qwen3.8 sampling; fix presence_penalty on the thinking alias
Sourced from upstream rather than tuned by hand. Qwen/Qwen3.8-27B card
'Best Practices' 1 and unsloth/Qwen3.8-27B 1 are byte-identical:

  Thinking: temperature=1.0 top_p=0.95 top_k=20 min_p=0.0
            presence_penalty=0.0 repetition_penalty=1.0
  Instruct: temperature=0.7 top_p=0.80 top_k=20 min_p=0.0
            presence_penalty=1.5 repetition_penalty=1.0

REAL BUG FIXED: gen-reasoning carried presence_penalty=1.5 -- the
INSTRUCT-mode value applied to a THINKING deployment, where canonical is
0.0. Corrected.

gen was already canonical; added the missing explicit min_p and
repetition_penalty so the full set is visible at the call site rather than
relying on backend defaults that happen to agree.

DELIBERATELY NOT canonicalised: summarizer, classifier, image-judge and
qwen-image-bench run temperature=0 (and the judges top_k=1,
repetition_penalty=1.05) because determinism is the point of those seats.
Forcing temperature=0.7 on a classifier to match a chat preset would break
their contract, so canonical is applied only where the alias is actually
doing open-ended generation.

Recorded against presence_penalty=1.5, which upstream itself hedges on
verbatim: 'you can adjust the presence_penalty parameter between 0 and 2
to reduce endless repetition. However, using a higher value may
occasionally result in language mixing and a slight decrease in model
performance.' 1.5 is high in that band and is the operator's suspected
trigger for the multi-turn degradation. Left at canonical so the baseline
is defensible, with the caveat and the 0.0-0.5 fallback documented inline
as the first dial to move if it recurs.
2026-08-16 16:01:22 -07:00
vh a81c44db04 config(gen-seat): reasoning_effort=medium default (affects gen-reasoning only)
Measured on the restored model: at the template default xhigh, reasoning
runs 4,529-5,532 chars on a 3-turn history and was observed spiking to
9,261; medium holds it to 2,602-3,283 with content length unchanged or
better. Per-request overridable; an invalid value 400s.

SCOPE CAVEAT, stated because I applied this while chasing the wrong path:
this is a NO-OP for the  alias, which sends enable_thinking:false and
produces zero reasoning. It affects  only. The operator's
reported multi-turn failure was on , so this does not address it.
2026-08-16 15:51:30 -07:00
vh 821f751870 revert(gen-seat): roll back to qwen38-27b-uncensored-nvfp4-mixed
Operator reports AEON-ULTIMATE degenerate on long MULTI-TURN conversations.
Restored the previous model and removed the reasoning_effort default in
the same change, so the model is the only variable differing from the
pre-trial state and the operator's comparison is clean.

My acceptance gate did not cover this failure mode and should have. Every
probe was SINGLE-TURN -- quickbench, concbench, surface_test, the
long-form smokes -- so a defect that only appears as conversation history
accumulates was structurally invisible to all of it. The gate measures
decode speed, MTP acceptance, abliteration survival, and a 36k needle, and
passes a model that degrades across turns.

Verified restored via docker inspect rather than the compose file:
/model -> qwen38-27b-uncensored-nvfp4-mixed, --default-chat-template-kwargs
absent, MTP drafter loaded, all 7 aliases answering.

AEON weights retained at /tank/aimodels/qwen38-27b-aeon-ultimate-nvfp4 for
diagnosis; bench artifacts stay in services/gen-seat-mixed-quant/bench/.
Its single-turn numbers were real (104.22 tok/s, 52.3% MTP, 4/4
abliteration, 6/6 surface) -- they were just measuring the wrong thing.
2026-08-16 15:23:39 -07:00
vh fb3bb521fe fix(gateway-chat): guard max_tokens against NaN from an empty field
An empty or non-numeric Max tokens field makes parseInt return NaN, and
JSON.stringify serialises NaN as null. The server reads null as 'no
max_tokens supplied' and substitutes its own default -- which is
indistinguishable from the UI ignoring the field, and is the most likely
explanation for a typed value appearing to have no effect. Falls back to
the same 4096 the input defaults to.

Ruled out on the way to this, all measured rather than assumed:
  - LiteLLM caps nothing: max_tokens=None on both aliases, no max-token
    keys in litellm_settings or general_settings.
  - The gateway honours large values end-to-end: 5,346 completion tokens
    returned at max_tokens=8192, finish=stop.
  - The UI has ONE chat send path, no duplicate element ids, a standard
    getElementById helper, and the request body is never mutated after
    construction -- so the field is read live at send time.

Remaining client-side cause if it recurs is a stale cached page: nginx
serves this file with only Last-Modified/ETag and no Cache-Control, so an
already-open tab will not re-fetch. ETag changes on each deploy, so a
reload picks it up.
2026-08-16 15:15:02 -07:00
vh b6552e0546 fix(gateway-chat): raise max_tokens default 1024 -> 4096; document single-file bind-mount trap
Operator reported the gen seat 'cutting off'. It is not the seat. The chat
UI's max_tokens field defaults to 1024, and every thinking seat spends part
of that budget on CoT before emitting content, so the completion truncates
mid-sentence with finish_reason=length and reads as model degeneracy.

Measured through the gateway:
  gen            1024 -> finish=stop, 716w   (survives, but marginally)
  gen-reasoning  1024 -> finish=length, cut mid-word  <-- the symptom
  gen-reasoning  4096 -> finish=stop, 839w

Seat itself is clean: direct long-form generations return finish=stop with
complete sentences and a max repeated 6-gram of 1 (no degeneration), and
enable_thinking:false still holds on every non-thinking alias, so the
AEON swap did not cause this.

Also documents a trap that made the fix look like it had not applied:
compose bind-mounts a single FILE, and a single-file bind mount binds the
INODE. rsync writes-and-renames, producing a new inode, so the container
kept serving the old content while the host file showed the new value --
silently, with no error. docker restart does NOT clear it; the container
must be recreated. Verify against what the container sees, never the host
file. Applies to any file-source mount; directory mounts are unaffected.
2026-08-16 15:10:27 -07:00
vh d47dd10795 feat(gen-seat): swap to AEON-ULTIMATE NVFP4 W4A4; default reasoning_effort=medium
Operator call: the incumbent abliterated model was the first one we could
find, not an optimised pick. sakamakismile/Qwen3.8-27B-AEON-ULTIMATE-
UNCENSORED-NVFP4 (base AEON-7 BF16, abliterix-abliterated, Apache-2.0),
byte-verified at /tank/aimodels/qwen38-27b-aeon-ultimate-nvfp4.

Measured on the same harness, same GPU, cache-busted per playbook 5.
Baseline was RE-measured live before the swap rather than trusted:

                        incumbent (W4A4+FP8 mixed)   AEON (W4A4)
  decode bs=1           94.09 tok/s                 104.22   +10.8%
  MTP acceptance        47.7%                        52.3%    +4.6pp
  abliteration          4/4                          4/4
  surface               6/6                          6/6
  weights               22.5 GB                      20.6 GB  -8.4%

AEON concurrency: conc=1 98.48 tok/s aggregate; conc=6 381.29 aggregate /
63.55 per-stream, MTP holding 50.6% under load.

Surface 6/6 includes vision (image-judge rides this seat) and a 36k-token
needle retrieval, which was the specific risk in going full-W4A4 -- the
packager only validated 32k, and W4A4 long-context collapse is in our own
notes from the Granite work. It held.

reasoning_effort: the AEON template defaults to xhigh (template line 47),
and at xhigh this model can spend its entire budget inside <think> and
emit no answer -- a silent-empty-response hazard for the automated
summarizer/classifier consumers. Seat now pins the default to medium via
--default-chat-template-kwargs, per-request overridable. Override PROVEN
live: chat_template_kwargs.reasoning_effort=bogus returns HTTP 400
carrying the template's own exception text, so caller values genuinely
reach the template and invalid ones fail loudly rather than silently
falling back. Empty GEN_REASONING_EFFORT omits the flag for models that do
not read the kwarg -- the Qwen3.6 line ignores it entirely, where setting
it would be a false lever.

All 7 aliases verified routing. Rollback is one .env line; the previous
build is untouched at /tank/aimodels/qwen38-27b-uncensored-nvfp4-mixed.

TWO GAPS, declared:
  - Incumbent concurrency was never captured before the swap (I baselined
    bs=1 only), so the conc=1/6 figures have no same-hardware comparator.
  - Perplexity NOT measured. eval_quality correctly refused it: under
    --speculative-config prompt_logprobs come back ~uniform (median rank
    ~130k), playbook trap 2. A real PPL number needs both seats served
    without spec-decode.

Adds concbench.py (concurrent throughput; wall-clock aggregate, not
sum-of-rates, and delta-based MTP accounting).
2026-08-16 14:56:37 -07:00
vh 0b95701173 memory: snapshot — FF A/B result, seat state, Qwen-thinking finding, esh hardening
Supersedes the 'blocked on a reproducing battery' entry: brokkr supplied
the framing, the A/B ran, and the refusal question is settled.

Most load-bearing fact for a cold session: Dark-Scarlett is DOWN and
Fable-Fusion is serving char-rp-reasoning on an evaluation window with no
permanent decision taken. Rollback recorded inline.
2026-08-16 10:29:39 -07:00
vh 55705ba650 feat(esh): harden esh-vm-docker against the NFS D-state wedge (Tier 1 + watchdog)
Root cause: all four NFS mounts were `hard`, so a NAS stall at 10.0.50.50
blocks I/O in uninterruptible sleep forever. The existing
x-systemd.before=docker.service fstab fix addressed the BOOT RACE -- a
different bug -- and never touched the runtime stall that keeps wedging
the box (2026-07-15, 2026-08-16).

Investigation narrowed the exposure well below what the parked item
assumed: only 2 of 12 containers touched NFS at all, and container state
was already on local disk (/var/lib/docker, 143G free).

Removed, no data risk:
  /mnt/compose (2.1G)   fully vestigial -- zero containers running or
                        stopped referenced it, dockge reads local
                        /opt/docker, and its one surviving mention was a
                        comment in beszel-agent-esh/.env describing a
                        DIFFERENT host.
  /mnt/documents (2.0K) paperless's consume/export spool dirs, verified
                        empty, moved to /opt/docker/data/paperless at the
                        same 0777 the container already saw. Recreated,
                        healthy.

Both commented out in fstab (backup /etc/fstab.bak-nfs-harden-20260816)
and unmounted. Wedge surface halved: 4 mounts -> 2, 2 wedge-capable
containers -> 1.

traefik needed no change -- already restart: unless-stopped, which is why
it self-recovered after the reset.

Watchdog on esh-pve (NOT in the guest -- a watchdog inside the thing it
watches is no watchdog). It probes traefik over HTTP rather than ping or
SSH because the wedge signature is 'guest OS alive, services dead': / is
local disk, so sshd answers and ICMP replies straight through a total
outage, and a TCP check would report HEALTHY. The guest-agent ping is
recorded only to classify the failure, never to veto a reset. 5
consecutive failures at 2-min interval (~10 min) then qm reset 100;
30-min cooldown against reset loops, acts only when qm status is running,
and honours /etc/esh-vm-docker-watchdog.disabled.

All four paths validated on install without power-cycling anything:
healthy -> silent no-op, disable flag -> SKIP, simulated outage -> counts
and classifies as the D-state signature, recovery -> counter cleared.

DEFERRED by operator ruling: /mnt/books stays `hard`. It holds calibre's
SQLite metadata.db and soft/softerr risks corrupting it mid-write. That
is the one remaining wedge vector; revisit alongside moving the library
off NFS.

Park item 28 promoted with full provenance.
2026-08-16 10:23:24 -07:00
vh 53096bffdc docs(litellm): correct stale char-rp description -- it is MeroMero/Gemma-4, not the retired GGUF Magidonia seat
The comment still described TheDrummer Magidonia-24B-v4.3 Q6_K on
llama.cpp, which was replaced by the vLLM MeroMero-v2 NVFP4A16 seat on
2026-08-12. Routing was already correct (:8016 is MeroMero); only the
prose was wrong, so anyone reading the config got the wrong model family
entirely.

Records why the seat exists: char-rp-reasoning is a Qwen3.x derivative and
emits ~5-6k chars of CoT per turn regardless of which Qwen RP tune is
loaded. Measured 2026-08-16 on identical prompts -- Dark-Scarlett 6036 ch
vs Fable-Fusion 5323 ch -- so that is the base family, not the finetune,
and no swap within it fixes it. Gemma-4 is the non-thinking seat.

Also pins the mandatory --default-chat-template-kwargs
'{"enable_thinking": false}' rationale from b8f0f4c, and flags that the
temp 1.1 / min_p 0.10 samplers were tuned against the retired
Mistral-family seat and never re-tuned for Gemma-4.

Docs-only: no litellm_params touched, no routing change.
2026-08-16 09:57:48 -07:00
vh ee2b678bcb feat(litellm): wire char-rp-reasoning + char-rp-fable to the Fable-Fusion seat
Operator-directed evaluation window. char-rp-reasoning now resolves to
Fable-Fusion 711 on :8019 instead of Dark-Scarlett on :8018; DS is DOWN
because GPU1 is zero-sum and FF occupies her slot.

This is an EXPLICIT substitution, not a silent alias swap: the config
block says so in place, carries the measured justification, and names the
rollback. char-rp-fable is added as the seat's honest name so the
evaluation can address it without depending on the temporary repoint, and
as a distinct model_name it gets its own litellm_params object rather than
sharing one (which is what bleeds sampler overrides between variants).

Samplers are unchanged from the DS entry and match the model card's
thinking-mode recommendation (temp 1.0 / top_p 0.95 / top_k 20). Verified
the FF chat template actually honours enable_thinking
(chat_template.jinja:44) rather than ignoring it -- the mismatch that
returned null content on the MeroMero seat.

Verified end-to-end through the gateway on both aliases: prose in content,
CoT in reasoning_content, finish=stop.

CONSUMER HAZARD: FF reasons heavily (2.1-4.6k chars). At max_tokens=1200
one of seven calls returned EMPTY content with finish_reason=length --
reasoning ate the whole budget. Not a refusal and not an alias fault. Use
max_tokens >= 3072; 6/6 clean there. No default is baked into the alias
because that would override caller intent silently.
2026-08-16 09:28:24 -07:00
vh b9e68c3fd2 feat(refusal-probe): three-arm A/B result -- Fable-Fusion 711 cuts refusals 92.5% -> 15.8%
brokkr-smithy-dev supplied the framing that reproduces the production
failure: a bare assistant-mode instruction, no character card, no
permission preamble. Added as battery-instruct.yaml, verbatim to his shape.
Refusal is monotonic in framing, and that third arm is the discriminator.

                                    DS v1.0      Fable-Fusion 711
  permission framing (temp 1.0)     0.0%  0/75   0.0%  0/75
  plain character cards (temp 1.0)  1.4%  1/74   0.0%  0/74
  bare instruction (temp 0.7)      92.5% 37/40  15.8%  6/38

Per-axis on arm 3, DS -> FF: incest 100->20, non-con 100->20, bestiality
100->25, necrophilia 100->40, gore 100->0, consensual 80->20, dubcon 80->0,
self-harm 80->0. DS refused 25/25 on the five axes brokkr flagged.

My own arm-3 measurement exceeded brokkr's 62.5% at n=16; his headline
under-read his own finding rather than over-reading it, though he had
already corrected the claim in the other direction.

Known battery bug, left unfixed for comparability: DS's arm-3 control gate
failed at 11% because ictrl-reunion pairs 'explicit / do not fade to black'
with brothers, which DS reasonably read as an incest request. FF did not
take that read. Both models faced an identical battery; ictrl-storm is the
clean control.

MTP loads on this model and survives RP temperatures: vLLM resolved
Qwen3_5MTP, loaded the drafter, shared embedding + lm_head. 47.0%
acceptance (229,169/487,725) over ~163k draft windows at temp 0.7-1.0,
1.41 extra tokens per window, ~80.6 tok/s decode at temp 1.0. This
CORRECTS the caution in the probe stack README that acceptance would fall
at RP temps -- it did not, and 47.0% matches the gen seat's 47.7%.

No decision taken; seat state reported separately.
2026-08-16 09:20:59 -07:00
vh 09c56d51a9 memory: DS-replacement eval blocked on a reproducing battery; refusal-measurement lessons
Records the open loop with brokkr-smithy-dev (01M05KQAD55PYHGCP668AV06YR)
and three durable lessons: benign controls cannot validate a refusal
classifier on in-character RP prose, a 0% rate needs a classifier
self-test before it is believed, and an approved plan whose premise the
evidence invalidates should be surfaced rather than spent.
2026-08-16 08:41:57 -07:00
vh 32f665e403 fix(refusal-probe): correct classifier false positives; Dark-Scarlett baselines at 0-1.4%
Baselined the live char-rp-reasoning seat twice, 5 samples x 17 prompts at
temp 1.0. With permission framing in the system prompt: 0.0% refusal (0/75).
With framing stripped to plain character cards: 1.4% (1/74). Dark-Scarlett
wrote every hard prompt in full at 2000+ tokens. The battery does NOT
reproduce the reported production failure.

The bare run first scored 9.5%. That was wrong -- classifier false positives:

  1. 'I cannot shift my weight' -- the CHARACTER speaking, 100 chars into a
     2,443-token scene. A genuine refusal opens with its marker, so the scan
     window is now the first sentence rather than 400 chars, and a marker
     followed by long prose demotes to AMBIGUOUS.
  2. 'Yeah, I'm an AI. If I were human, I'd be asking for a retainer...
     What's the actual gig?' -- an in-character answer that keeps driving the
     scene. AI self-acknowledgement is no longer scored as a refusal on its
     own; real refusals carry a separate hard marker regardless.

Generalisable trap now documented: benign controls catch a classifier
over-firing on benign prompts, but cannot catch it over-firing on
in-character prose, and first-person RP is full of 'I can't'.

Adds test_classify.py (16 cases, both false positives pinned as
regressions), battery-bare.yaml, and probe.py --rescore to re-classify a
saved run with no inference. The single surviving refusal is
self-contradicting: DS recites 'I don't generate explicit sexual content,
graphic violence...' in the same run where she generated all three at 0%.

Seat untouched -- no swap performed, no downtime taken, GPU1 unchanged.
2026-08-15 23:40:52 -07:00
vh dd627b3b31 feat(refusal-probe): A/B refusal harness + Fable-Fusion 711 probe seat
Dark-Scarlett v1.0 refuses too much on the char-rp-reasoning seat. Root
cause is visible on its card: ReadyArt/Dark-Scarlett-v1.0-27B is a plain
finetune of stock Qwen/Qwen3.6-27B, tagged unaligned/nsfw/erp but carrying
no abliteration -- the base model's refusal machinery is intact, so
off-distribution prompts revert to safety-tuned Qwen3.6 behaviour.

Candidate kkuspa/Qwen3.6-27B-Fable-Fusion-711-...-NVFP4A16 is refusal-ablated
(Heretic), a structural edit rather than a behavioural preference. Verified
before pulling: Qwen3_5ForConditionalGeneration wrapper class, 15 mtp.*
tensors in a separate bf16 shard AND individually enumerated in
quantization_config.ignore, NVFP4A16 with null input_activations, FP8 KV
scales shipped, 262K context, Apache-2.0. Staged byte-verified at
/tank/aimodels/fable-fusion-711-nvfp4a16 (28.55 GB).

services/refusal-probe: deterministic marker-based classifier (LLM judge
only breaks AMBIGUOUS ties, never overrides), intensity-graded battery so
the report renders a refusal curve rather than an average, benign controls
that gate run validity, and explicit handling of the thinking-budget trap
-- empty content with finish_reason=length is reasoning exhausting the
budget, not a refusal, and is excluded from the denominator.

stacks/fablefusion-charrp-probe: throwaway :8019 seat serving as
char-rp-probe, never aliased to char-rp-reasoning. MTP depth 3 rather than
the card's 5 -- its 1.56x was measured greedy, and acceptance degrades at
the temp 1.0 this seat is probed at. GPU1 is zero-sum at 94.9/97.9 GB, so
this seat takes Dark-Scarlett's vacated slot; the A/B is sequential.
2026-08-15 23:14:05 -07:00
vh f83456a276 memory: snapshot for /clear — auto-archived 16 entries (Recent decisions 13, Tried and abandoned 3); index 303 -> 254 2026-08-15 19:55:40 -07:00
vh 4e74e0aefe memory: RP-seat direction — keep MeroMero, reject Artemis, Dark-Scarlett 3.8 queued as the MTP-restoring move 2026-08-15 19:50:11 -07:00
vh f338f228a6 memory: record the quantization playbook as the durable home for quant lessons 2026-08-15 10:02:24 -07:00
vh a91cc3fb38 docs(quant): consolidate quantization lessons into a durable playbook
Quants are hard-fought and we keep re-paying for the same lessons. A survey
found quant knowledge scattered across 18 files in four trees, with three
documents having independently discovered and recorded overlapping
"landmines" sections — and one of them now actively misleading.

Adds docs/pfi/model-quantization-playbook.md as the single home for the
TRANSFERABLE lessons, with per-model artifacts demoted to worked examples
that link up to it. Contents:

- scheme decision table, incl. that a literal "W4A8" NVFP4 checkpoint is
  unservable on vLLM (two legal activation settings, FP8 is not one)
- the reference mixed-precision recipe and the three parts of it that are
  load-bearing and easy to drop
- the recurring landmines, ordered by cost: the loader-class trap
  (rediscovered THREE times), the three separate ways to lose the MTP head,
  toolchain deadlocks, vision configs, memory/device placement
- pipeline shape: prove targets before spending GPU time; mandatory
  post-steps that verify rather than assume
- the acceptance gate, and the three ways measurement has lied to us —
  prefix caching faking both speed metrics, prompt_logprobs going uniform
  under speculative decoding, and a 0600 .env making compose silently no-op
- hardware/co-residency, including that a SMALLER model can starve its
  neighbour because gpu-memory-utilization is a fraction of the whole card
- a superseded-claims table, and measured negatives not to re-chase

The superseded table earns its place immediately: the heretic2 runbook tells
readers to use modelopt because "compressed-tensors can't load the BF16 MTP
head, 0% acceptance". That symptom was real but the cause was not the format
-- it was the missing re:^mtp.* ignore entry. compressed-tensors gives
47.7-83.2% acceptance in production. A fresh session following that doc would
be sent down the modelopt path that current memory calls dependency hell, so
the runbook now carries a stale-warning header pointing here.

Wires discovery: an orientation.md "Where to look for what" row, pointers
from the gen-seat / heretic2 / mistral artifacts, and a CLAUDE.md maintenance
rule so the playbook gets fed instead of going stale -- model-agnostic
lessons land in the playbook, model-specific ones stay put, and a wrong
claim earns a dated superseded row rather than a silent edit.

Motivated by Qwen3.8 having just released: the next model swap will need a
requant, and this is what that session should read first.
2026-08-15 10:02:12 -07:00
vh b9da05aeb2 memory: snapshot — overnight arc pushed; operator ruled the +1.7% PPL an acceptable trade (settled) 2026-08-15 08:14:05 -07:00
vh 930197a56a memory: record prefill result (roughly doubled) on the gen-seat mixed requant 2026-08-15 02:34:04 -07:00
vh 4a5c3fcccf perf(gen-seat): record prefill measurements — roughly doubled
Closes the one axis of the original premise left unverified. Measured
cold (cache-busted) on both builds under matching serve configs:

  ~6.7k-token prompt   3,206 -> 6,334 tok/s prefill   (+98%)
  ~27k-token prompt    2,862 -> 5,085 tok/s prefill   (+78%)
  TTFT on a ~27k doc    9.43 -> 5.31 s                (-44%)

Prefill gains far exceed the +18% decode gain, and that ordering is the
expected one: decode at bs=1 is memory-bandwidth-bound and the weights
are 4-bit under either scheme, so little changes; prefill is
compute-bound, which is where native Blackwell FP4 tensor cores replace
the Marlin dequant-to-BF16 path. The summarizer aliases are the
consumers that feel this.

Adds bench/prefill_bench.py plus the raw JSON. The harness deliberately
uses SystemRandom: a seeded nonce regenerates the previous run's prompts
verbatim, prefix caching then serves them, and the first attempt read
~41k tok/s of cache-hit rather than ~5k of actual prefill.
2026-08-15 02:33:50 -07:00
vh fa4f652a39 memory: snapshot — gen seat mixed NVFP4+FP8 requant (+18%) + char-rp tool parser; both queued items closed 2026-08-15 02:22:58 -07:00
vh 74f596b1d3 feat(gen-seat): mixed NVFP4+FP8 requant — +18% decode at equal MTP acceptance
Re-quantizes the fleet `gen` seat from weight-only NVFP4A16 to a
mixed-precision build: NVFP4 W4A4 for layers 0-55 MLPs, FP8 W8A8 for the
attention projections / linear_attn / lm_head / layers 56-63 MLPs, FP8 KV
cache. Replicates the scheme of unsloth/Qwen3.8-27B-NVFP4 on the
abliterated weights.

The queued task named this "W4A8" (NVFP4 weights + FP8 activations). That
checkpoint cannot be served: vLLM 0.24's compressed-tensors dispatcher
(compressed_tensors.py:704-713) accepts NVFP4 weights with either no input
quantization (W4A16, which forces the Marlin kernel) or NVFP4 input
quantization (W4A4) -- anything else, FP8 included, raises ValueError at
load. CompressedTensorsW4A8Fp8 is INT4 weights gated on an exact-sm90
check, so it is closed on Blackwell twice over. The ~20% intuition was
correct; the scheme name was not. Getting FP8 into the mix has to be done
per-layer-group.

Established the gain before spending GPU time: unsloth's build was already
on-box, so serving it as a probe measured +19.1% over our seat at identical
MTP acceptance -- a kernel-level result, no requant needed to learn it.

Measured, cache-busted, bs=1:

  decode              80.12 -> 94.53 tok/s   (+18.0%)
  MTP acceptance      47.8% -> 47.7%         (unchanged)
  perplexity (n=6)    6.941 -> 7.059         (+1.7%)
  abliteration        4/4   -> 4/4           (preserved)
  weights on disk     27.7  -> 22.5 GB       (-19%)

Surface test green on the live seat: plain chat, vision, tool calling,
thinking split, 36K-token needle retrieval, streaming. All 7 LiteLLM
aliases verified routing.

GEN_GPU_MEM_UTIL 0.45 -> 0.43: the new weights are 5.2 GB smaller, and at
0.45 the seat absorbed that slack as KV, leaving meromero-charrp 0.18 GiB
short of its budget on the shared GPU0 -- it crash-looped. Handing the
space back leaves gen 422K tokens of KV (1.6x its 262K context) and both
seats co-resident at 89.8/97.9 GB.

Also records two measured negatives so they are not re-chased:
GEN_SPEC_TOKENS is already optimal at 3 (swept 2/3/4/5 -> 77.1/80.1/78.7/
75.9 tok/s), and vLLM's prompt_logprobs are ~uniform while speculative
decoding is on, so perplexity must be measured with spec off.

Pipeline, acceptance harness and raw measurements land in
services/gen-seat-mixed-quant/. Rollback is one .env line; the previous
build is untouched at /tank/aimodels/qwen38-27b-uncensored-nvfp4.
2026-08-15 02:21:00 -07:00
vh b8f0f4c568 fix(char-rp): enable Gemma-4 tool-calling on the MeroMero seat
The char-rp seat shipped with no tool-call parser at all, so every
tools-bearing request was rejected outright:

  400 "auto" tool choice requires --enable-auto-tool-choice and
      --tool-call-parser to be set

MeroMero-v2 is Gemma-4, which emits its own native
`<|tool_call>call:name{...}<tool_call|>` syntax rather than the
qwen3_coder XML the Qwen-family seats use. vLLM 0.24 ships a matching
`gemma4` parser whose TOOL_CALL_START/END, CHANNEL_START/END and escape
token constants line up with this tokenizer's etc/eoc/escape tokens
exactly.

Three flags, and they are a set:

- --tool-call-parser gemma4 + --enable-auto-tool-choice: the fix proper.
- --reasoning-parser gemma4: without it the post-tool-response turn
  leaks a literal `<|channel>thought\n<channel|>` prefix into content
  (upstream vllm #45834 — the chat template leaves the prompt inside an
  open channel block).
- --default-chat-template-kwargs '{"enable_thinking": false}': MANDATORY
  companion to the reasoning parser. The parser reads enable_thinking
  from chat_template_kwargs and defaults it to True
  (vllm/parser/gemma4.py:439); True makes is_reasoning_end() return
  False at a new turn, pre-initialising the engine to REASONING, which
  routes ALL plain RP prose into reasoning_content and returns a null
  content — breaking every char-rp consumer. This template already
  defaults enable_thinking to false (chat_template.jinja:350), so
  passing it explicitly renders a byte-identical prompt (verified across
  plain / tools / post-tool-response / system-prompt shapes). It changes
  generation not at all; it only corrects the parser state machine.

Verified green on the live seat after deploy: tool call streaming and
non-streaming, tool-result round-trip (leak gone), plain prose in
content with reasoning null, vision unchanged.
2026-08-15 00:51:29 -07:00
vh b1370e4b4d memory: snapshot — uncensored gen seat landed (Qwen3.8-27B-Uncensored, gen-seat/vllm-gen); next = chase W4A8 2026-08-15 00:32:50 -07:00
vh 680c30e778 gen-seat: deploy Qwen3.8-27B-Uncensored gen seat, rename qwen36-27b-aeon->gen-seat
- New uncensored gen seat: JonathanColetti/Qwen3.8-27B-Uncensored, in-house NVFP4
  W4A16 (compressed-tensors) + grafted bf16 MTP (config ignore re:^mtp.*), vision-intact,
  262K ctx, MTP n=3 (~42% accept, ~68 tok/s). Replaces the qwen3.6-35b-a3b-heretic MoE.
- Rename compose project qwen36-27b-aeon -> gen-seat, container vllm-aeon-gen -> vllm-gen,
  env vars AEON_GEN_* -> GEN_*; drop the dormant vllm-aeon-rp service.
- litellm: repoint 7 aliases (gen/summarizer/summarizer-large/classifier/image-judge/
  qwen-image-bench -> qwen3.8-27b-uncensored; gen-reasoning -> -thinking).
- servers/ana-ml2/README: refresh the gen hero-seat row.
2026-08-15 00:19:20 -07:00
vh dac4acf0c5 fix(booth): lazy-load gallery media — preload=none on video/audio
A booth of a dozen webms fetched them all at page load
(preload=metadata still pulls real byte ranges per file); nothing
loads now until the viewer hits play.
2026-08-14 23:18:47 -07:00
vh f6acb90d00 park(migration): promote the henge to permanent home on ana-docker
Operator-directed (Vuong 2026-08-13): migrated stonehenge-park off the nh3-docker
beta deployment to a permanent fixture on ana-docker (10.250.50.70) before the
v1.0.0 final cut. SQLite (park-data) migrated consistently (stop -> tar-copy ->
start; byte-identical). restic auto-covered by ana-docker's /var/lib/docker/volumes
source. Stable name park.phasefinal.com -> 10.250.50.70 (Cloudflare DNS-only) so
clients decouple from the host IP. Homepage tile 'The Henge' added. nh3-docker stack
left stopped as rollback pending park-dev cutover verification.
2026-08-13 14:28:42 -07:00
vh 992b6b10f0 homepage(tile): add Voice Design Studio (irv-ml1:8216) to Apps group
Operator-facing voice mint/audition/keeper-mark front-end (tts-dev), sited next
to The Booth in Apps — LAN/WG-internal, no auth. siteMonitor on /health verified
reachable from the homepage host (esh-docker-vm). Deployed via rsync.
2026-08-13 09:06:44 -07:00
vh 0dcce02e47 stacks(park): mirror stonehenge-park v1.0.0-beta.1 deploy on nh3-docker
New self-contained stack (FastAPI + SQLite + in-process scheduler) from
vh/stonehenge-park tag v1.0.0-beta.1, deployed to nh3-docker per park-dev's
operator-approved request. Port 8420, LAN/WG-internal; park-data volume (SQLite
sole source of truth) covered by the host's /var/lib/docker/volumes restic source.
Image built locally (no registry yet); .env carries PARK_API_KEY from the vault.
althing push to henge-crow deferred (PARK_ALTHING_CHANNEL empty) until althing-cli
is wired into the container.
2026-08-12 18:48:22 -07:00
vh 9fe7479ddc fix(gateway-chat): honor UI endpoint/model/voice for TTS + standard-audio playback
The TTS path was hardwired to the parked zonos-gateway: it force-reverted the
endpoint field back to zonos :8890 on load, hardcoded model=ext-tts, and decoded
the response as Zonos-specific raw float32 PCM @ 44.1kHz. Result: quoted-text TTS
was dead once zonos was parked, and pointing the field elsewhere silently failed.

- Honor the interface: set endpoint/model/voice defaults only when a field is
  empty; never rewrite a user-typed value (removed the zonos auto-revert regex).
- Add a TTS model field (ttsModel); send the UI's model instead of hardcoding.
- Playback: request standard OpenAI /v1/audio/speech mp3 and decode via
  audioCtx.decodeAudioData (handles wav/mp3/ogg/flac from any endpoint).
- Defaults: endpoint = LiteLLM ext-tts alias (fleet TTS gateway), voice = nova.
2026-08-12 17:31:04 -07:00
vh bf915e15f0 memory: snapshot — eRP dual-seat overhaul landed (MeroMero + Dark-Scarlett @ 256K); next = infra+access 2026-08-12 14:33:53 -07:00
vh f08b6cbddf stacks(char-rp): compose-ify the two NVFP4 RP seats (MeroMero + Dark-Scarlett)
Replaces the ad-hoc docker-run seats with proper compose stacks on ana-ml2, mirrored here:
- meromero-charrp: G4-MeroMero-v2-31B NVFP4A16, char-rp prose (non-thinking, multimodal,
  vision-enabled), GPU0, 256K @ ~2x. util 0.52 (leaves ~4.6GB GPU0 headroom).
- darkscarlett-charrp-reasoning: Dark-Scarlett-v1.0-27B NVFP4A16 (Qwen wrapper recipe),
  char-rp-reasoning thinking seat, GPU1, 256K. MTP deferred (no spec-decode).
Both survive reboot now. Supersede the retired char-rp-gguf + heretic2-charrp-reasoning stacks.
2026-08-12 11:15:33 -07:00
vh 398b58a161 stacks(wgtunnel): mirror deployed wstunnel server stack from ana-docker
wgtunnel deployed + accepted end-to-end (tunnel-dev): erebe/wstunnel v10.6.2 behind
traefik on ana-docker, Host boring.phasefinal.com (Mode A anaprod cert), --restrict-to
ana-wg:31337 (not an open relay). Mirror per fleet convention; full project in vh/wgtunnel.
2026-08-12 10:01:46 -07:00
vh 7bd7375d65 docs(pfi): add SOTA eRP thinking-finetunes research (gecko-65 booth → reference)
56KB deep-research report on thinking-capable eRP finetunes 15-30B, weighted for
spatial/state coherence, targeting RTX PRO 6000 Blackwell (sm_120) NVFP4. Preserved
from an ephemeral Booth (gecko-65) into durable reference for the quant decision.
2026-08-12 02:05:22 -07:00
vh 69597cb686 memory: wgtunnel Phase-0 DNS landed + infra-ops now holds fleet Cloudflare DNS-edit token
boring.phasefinal.com CNAME -> ana-srv1 (DNS-only) published + verified; unblocks
the wstunnel ACME cert. Operator handed over an all-zones Zone:DNS:Edit token, now
vaulted at nh3-dev/.config/cloudflare/infra-ops-dns-token — fleet DNS is self-serve.
2026-08-12 01:29:42 -07:00
vh a8c6d85df9 memory: wgtunnel moved to its own repo (vh/wgtunnel) — endpoint infra verified + pre-seeded
Flip the queued-project pointer: wgtunnel is now a standalone repo, pre-seeded
after a live verification pass (ana-wg UDP/31337 endpoint, traefik TLS edge on
ana-docker, direct-A public edge). Ground truth captured in the new repo;
/vor-plan is the next step. Do wgtunnel work in ~/development/wgtunnel, not here.
2026-08-11 23:48:17 -07:00
vh 3b7e10cd29 memory: snapshot for /clear — secrets-broker + stonehenge-park landed; wgtunnel queued next
Current state rewritten to session-end: secrets-broker LIVE (secret CLI + 25-item
backfill + rm/warn), stonehenge-park /park service repo stood up (vor-plan+vor-ui),
dots-tts extracted to tts-stack; NEXT PROJECT = wgtunnel (WireGuard over a plane's
wifi). Two detail files added; 2 oldest T&A entries archived. Handoff written to
/tmp/infra-ops-handoff.md for the post-clear pickup.
2026-08-11 23:25:31 -07:00
vh a1304b7812 docs(secrets-broker): record deferred 'secret put' scripted-namespace edge
jackdaw-dev observation (2026-08-12): the new-namespace warning is non-blocking +
stderr, so a scripted put suppressing stderr can still mis-namespace silently.
Deliberately not blocking (domain-scoped names would misfire on auto-prefix);
revisit with an opt-in --strict flag only if scripted callers appear.
2026-08-11 23:02:58 -07:00
vh a249073a08 feat(secrets-broker): add 'secret rm' + a new-namespace heads-up on put
Both from jackdaw-dev feedback after a mis-namespaced item (missing host prefix)
hid under a prefix nobody searches:
- 'secret rm <name>' — delete an item by exact name (bw soft-delete to trash,
  recoverable); closes the 'no delete path, append-only in practice' gap.
- 'secret put' now warns (stderr, non-blocking) when a name opens a brand-new
  top-level namespace, listing existing ones + suggesting the host prefix —
  catches a typo'd/missing prefix at store time.

Installed copy at ~/.local/bin/secret synced.
2026-08-11 22:58:33 -07:00
vh 850a1976d5 feat(secrets-broker): nh3-dev backfill complete (25/25) + attachment + resilient run
Scope corrected to per-dev-box (CC sessions on this box), not a fleet service; each
box duplicates the stack and backs up its own local secrets, hostname-namespaced.

CLI:
- backfill is local-only (scan this box's ~/development/*/{env.sh,.env} + ~/.config
  credentials; exclude bootstrap.env/examples/AIPA-Data archives).
- large files (>6000 B) route to a bw ATTACHMENT instead of the note field
  (Vaultwarden caps notes at ~10000 encrypted chars); get/verify read it back.
- backfill catches per-item failures and continues (bw errors raise BwError,
  main converts to a clean exit); idempotent upsert makes re-runs safe.

Backfilled all 25 nh3-dev secret files into the infra-ops org's Default collection
(folder = hostname), every one round-trip verified (2 large via attachment, 23 via
note). README added for duplicating the stack to new dev boxes. Contract scope +
data-model sections updated (bw, org/collection, per-box).
2026-08-11 16:35:06 -07:00
vh 41359eaff9 feat(secrets-broker): secret CLI (bw-backed fleet credential store) + contract
secret put/get/list/backfill over Vaultwarden via the bw CLI. Items land in the
infra-ops org's Default collection (visible to the operator's primary account via
org share), organised by folder + <host>/<stack>/<file> naming; text in the note,
binary base64'd into a hidden field; sha256 + source metadata fields; idempotent
upsert keyed by name. Auth bootstraps from ~/.config/secrets-broker/bootstrap.env
(0600, apikey login + master-password unlock, per-invocation session).

Verified live end-to-end (create/upsert/get-note/get-field/list). Contract updated:
bw replaces rbw (rbw register 400'd undebuggably despite valid creds).

Known limitation: bw-subprocess-per-op is ~3s/call → ~15-25s/command; too slow for
a fleet-scale backfill. Next: a bw serve broker (fast + central-cred fleet model).
2026-08-11 16:02:30 -07:00
vh 62672c9850 refactor(dots-tts): extract TTS stack to tts-stack repo; pointer stub + move voices out
TTS development moves to a dedicated repo (~/development/tts-stack) so a separate
agent can own tuning/dev. Mirrors the chatterbox-fast extraction:

- stacks/dots-tts/ reduced to a pointer README (code/Dockerfile/compose/tests/env
  now canonical in tts-stack).
- voices/ canonical corpus moved out to tts-stack/voices/. Blast-radius checked:
  no eshpfi playbook/script reads the corpus (other voices/ refs are unrelated
  host paths under /worktank/...).
- persistent-memory updated: TTS dev extracted + stood down; reverses the earlier
  "corpus home = eshpfi voices/" call.

The ~15 experimental TTS compose wrappers stay here as reference (catalogued in
tts-stack/KNOWLEDGE.md). Live service on irv-ml1:8198 is unaffected (runs from a
copy on the host).
2026-08-11 07:46:11 -07:00
vh a80f6e958f fix(dots-tts): v3 — clause-break (; : em-dash) → period pause mapping
dots' prosody honors a pause only for ellipsis (~+0.43s) and period (~+0.3s);
comma/semicolon/colon/dash all run flat (~+0.03s vs no-punct), measured via a
duration-over-N-runs pause probe against the live service. Two sub-causes for
the flat clause reads: em-dashes regressed in v2 (the —→- fold made them read
as word-joiners), and semicolons were never honored by dots at all.

Operator ruled ellipsis "too much" → map semicolon, clause colon, and em-dash
to a period in _sanitize (believable ~0.3s clause pause). Guards, pinned by
tests: digit-guarded colon so times (3:45) and ratios (2:1) keep their colon;
en-dash kept folding to hyphen so numeric ranges (10–20) don't become "10.20";
a genuine ellipsis retains its strong pause.

Deployed to irv-ml1:8198 as local/dots-tts:v3 via the redeploy2 build →
:8199-test → pause-gate → cutover pattern (gate measured +0.427s, live healthy).
2026-08-10 21:54:35 -07:00
vh 944c22a95c memory: snapshot for /clear — dots.tts engine arc + LFM2.5 landed; in-flight refreshed to session end-state 2026-08-10 15:07:18 -07:00
vh 077570167f memory: dots-tts v2 consumer-confirmed clean (ratatoskr end-to-end verify) 2026-08-10 09:15:08 -07:00
vh 10d379db5b fix(dots-tts): v2 — curly-punctuation sanitize + sentence-chunking
Curly apostrophes (ratatoskr's LLM emits typographic punctuation) made dots
mispronounce contractions ("Donut's"->"donut ess"); fold curly->ASCII before
synth, keep normalize_text on. Add server-side sentence-chunking so long turns
stop truncating at dots' ~40s single-generate cap (verified full 160s Zev).
Dockerfile: pin dots.tts==0.2.1 + torch/torchaudio==2.8.0 (upstream constraints
now pin a phantom gradio==6.17.0; float torchaudio->2.11.0 crashes the load).
2026-08-10 09:11:56 -07:00
vh d3727dee53 feat(vllm): lfm2.5 reasoning-parser (deepseek_r1) — scoreable JSON for brokkr's bake-off
LFM2.5 is </think>-delimited (opening tag in prompt); deepseek_r1 splits
reasoning into reasoning_content so content is the clean post-</think>
answer. Re-smoke: content valid JSON + reasoning_content populated. License
production-cleared (operator <$10M ruling), still out of routing per the
measurement gate.
2026-08-10 07:27:36 -07:00
vh bb65f36f70 memory: chatterbox-fast :8197 reclaimed (dots migration fully closed) — 3090 freed ~7GB 2026-08-10 07:19:28 -07:00
vh fa6e9a3c69 memory: dots-tts ratatoskr cutover COMPLETE (operator ear-confirmed) — chatterbox :8197 reclaim pending operator go 2026-08-10 07:17:35 -07:00
vh b846ebf32e memory: ratatoskr dots-tts cutover shipped (v0.22.2) — hold :8197 rollback pending operator ear-check 2026-08-10 07:15:22 -07:00
vh edc9f42da1 feat(vllm,litellm): lfm2.5-2.6b non-prod bake-off alias for brokkr
vllm-lfm25 on ana-ml2 GPU1 :8021 (LiquidAI/LFM2.5-2.6B, util 0.09 into
unreserved slack, max-len 16384, no reasoning-parser so content is non-empty).
LiteLLM alias lfm2.5-2.6b with vendor sampling baked as default (temp 0.1;
top_k 50 + repetition_penalty 1.1 via extra_body). Eval-only, not in any
routing chain, pending operator ruling on LFM Open License production use.
2026-08-10 07:13:37 -07:00
vh c8acf60449 feat(dots-tts): ship OpenAI-compatible dots.tts TTS stack on irv-ml1:8198
Thin FastAPI wrapper over DotsTtsRuntime (soar, optimize=True, RTF ~0.22),
serialized single-consumer; OpenAI /v1/audio/speech (stream + non-stream),
voices from the voices/ corpus derived set. Live + healthy alongside
chatterbox-fast on the 3090; nothing repointed. Dockerfile needs
build-essential (torch.compile/inductor JITs via gcc at runtime) + persisted
inductor cache. Remaining Phase-2: ratatoskr client cutover.
2026-08-10 01:07:37 -07:00
vh fca1a545f1 feat(voices): canonical voice corpus + dots.tts-optimized refs
Engine-agnostic voice corpus: canonical source clip + transcript per voice,
per-engine reference sets derived by derive.py from engines.yaml profiles.
First residents donut/glados/emmie/miranda optimized + verified clean for
dots.tts (sentence-bounded ref + accurate transcript — dots leaks reference
audio into output otherwise). canonical/ + transcripts/ tracked; derived/
gitignored (regenerable). Records the dots.tts burn-in in persistent-memory.
2026-08-10 00:32:10 -07:00
vh 58b58d1401 memory: worldtree #400 closed — fiction-decomp snapshot cleared (208M); ratatoskr-dev knob revert now sole non-blocking await 2026-08-08 20:37:47 -07:00
vh ba4597b8f2 memory: snapshot for /clear — session arcs landed (kb sweep, muninn 0.1.6, chatterbox-fast tail-degradation fix, Zonos-down, #400 pull); in-flight trimmed to awaiting-peers 2026-08-07 19:15:03 -07:00
vh 6399a5a267 memory: record #400 personal-KB Chroma snapshot on nh3-dev (worldtree-dev pull)
Read-only .chroma persist store pulled to ~/snapshots/worldtree-400-fiction-decomp/
(fiction 1578 / kb 2876 post-#394 / main 3224), provenance-marked, keep until #400 done.
2026-08-07 18:53:12 -07:00
vh 6332f14af5 memory: chatterbox-fast tail-degradation diagnosed + fixed (max_chunk_chars=250 cap)
Long operator-driven diagnosis: the 'broken/German/dead-air' was the Turbo T3
model over-running its generation tail (garble in final ~2-3s, worse with
length + tight sampling), NOT a language leak or OOM. Fix = server-side
max_chunk_chars=250 cap (:v2), keeping 3-4 sentence clean chunks with smooth
joins. Method (amplitude-gated voiced-ZCR), foot-guns (tail-trim unreliable,
build-context/image drift), and the flat-vs-repo divergence recorded.
2026-08-07 11:40:05 -07:00
vh 2d7eb90cc3 memory: Zonos2 taken down on 3090 (operator-directed, for memory, temporary)
Freed ~17.4GB (3090 728MiB->18.2GB free) for co-resident chatterbox-fast
(was OOMing). Detached native process; GPU mem held by multiprocessing-fork
children that orphan to init - kill children explicitly. Restore cmd +
affected consumers (asset-engine, gateway-chat) recorded.
2026-08-07 10:29:48 -07:00
vh 2e0bb85906 memory: chatterbox-fast donut voice added + contract delivered to ratatoskr-dev
Operator-directed donut add (zonos Donut.wav -> chatterbox /refs/donut.wav,
live glob no restart, verified 7.5s synth). Answered ratatoskr's 8-Q contract
ask for their TTS migration off Zonos: POST /tts (not OpenAI), no affect dials
(Turbo no-ops), streaming shape identical to Zonos, 24kHz, English-only.
2026-08-07 08:41:15 -07:00