Commit Graph

276 Commits

Author SHA1 Message Date
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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 ad2df89c0c feat(litellm): repoint fleet reranker alias to bge-reranker-v2-m3 (Brokkr R43)
The incumbent Qwen3-Reranker-0.6B was measured actively harming 80/90
fleet queries on main+knowledge_base (and inverting the bare-name region
behind Worldtree #389) — no-reranker beat it 89/90 vs 56/90. Brokkr's R43
bake-off selected BAAI/bge-reranker-v2-m3 (A3): 90/90 top-10, mean rank
0.19, multilingual (XLM-R), ~1.2 GB lighter than the incumbent.

Control arm (A2 = same Qwen weights, seq-cls head) scored identical to the
incumbent, proving the fault is a training prior, not the serving head —
which cancelled the expensive Qwen3-4B arm before it cost a GPU seat.

Cutover boundary 2026-08-06T17:37:48Z. The qwen3-reranker alias and the
:8002 backend are retained for one-edit rollback. Adds the process audit
trail at docs/pfi/reranker-selection-ledger.md.
2026-08-06 10:40:26 -07:00
vh 8a824f85a3 docs(muninn-gate): Dockerfile comment — dispatch pin now 0.1.5, SHA-tag note
Gate rebuilt off vh/muninn-gate main (bc04c4c) for the muninn-dispatch
0.1.4->0.1.5 bump (concept_schema/concept_schema_source row fields). Gate
version unchanged at 0.0.14; note to also tag the image with the source SHA
for traceability, and that the pin is authoritative in pyproject.
2026-08-01 17:27:14 -07:00
vh 0441995ac8 docs(muninn-gate): sync example to reality — mimir-inbox key control scope + staging live (#377)
The mimir-inbox deploy changed two things the committed example documents:
the mimir-inbox key gained the control scope (2026-08-01, for cancel/retry),
and the staging root is no longer a placeholder — it's the real shared dir on
corviduo-dev, path-agreement probe PASS (muninn-dev). Bind was already correct
at :8090 (the stale :8080 was only in the gate repo's own example).
2026-08-01 14:36:31 -07:00
vh 4b54a32d64 feat(mimir-inbox): book-ingestion UI stack on corviduo-dev:8091 (#377)
WG-internal FastAPI+HTMX front end for large-document ingestion into the
Muninn KB, over the muninn-gate API (browser -> mimir-inbox -> staging ->
path-addressed POST /jobs). Co-located on corviduo-dev with the gate (:8090)
and the worldtree-personal muninn watcher per the operator's 2026-08-01
co-location ruling (reversing the earlier off-box/NFS plan; worldtree-dev
approved the box placement).

- Dockerfile: python:3.11-slim + uv sync --no-dev --frozen (--no-dev is
  load-bearing; the dev group's muninn-dispatch path source is absent in-image
  and INV-MI-7 forbids importing it). Single-stage by design — src/ stays in
  the final image (uv installs the project editable-linked to src/).
- compose.yaml: uid 1000, host-net bind 10.250.50.152:8091, staging :rw,
  TCP-liveness healthcheck (deliberately not coupled to gate reachability).
- Built from vh/mimir-inbox HEAD c8ab38f; deployed + healthy.

Records the open-in-place claim semantics (worldtree-dev, runner.py:362-367)
and the INV-MI-19 retention rule (staged files persist until job terminal;
gate retry returns a false-200 on a swept source) in persistent memory.
2026-08-01 14:31:14 -07:00
vh 786462ac9c feat(muninn-gate): WG-internal Muninn ingestion front door stack (#377)
Deployed on corviduo-dev, co-located with the worldtree-personal muninn
watcher. ingestion_root=/data/state/ingestion (shared state volume, byte-
identical to the watcher); runs as uid 1000 to write the queue; staging
bound :ro at the ratified /mnt/muninn-staging/mimir-inbox (local placeholder
until the shared mount + mimir-inbox writer land). Boot verified: /ping
{"service":"ok"}, /health watcher.running=true (byte-identity proven).

Image built out-of-band with the Gitea read token as a BuildKit secret.
Real config (bearer keys) lives on-server at /opt/docker/conf 0600.
2026-07-30 21:33:29 -07:00
vh 792aa2852c docs(zed-fim-proxy): source-IP allowlist stays OFF by design (Zed roams WG 10.0.0.0/8) 2026-07-27 22:58:22 -07:00
vh a300cdcd26 feat: Zed edit-predictions keyless FIM route (Qwen2.5-Coder-1.5B / coder-fast)
Deep-research-picked Qwen2.5-Coder-1.5B (BASE, Apache-2.0, native FIM) as a
low-latency inline-completion seat:
- stacks/vllm: vllm-coder service (ana-ml2 GPU1 :8020) + granite shrunk
  (util 0.27->0.13, max-len 131072->16384, seqs 1024->256; granite phasing out)
  to free GPU1 room.
- stacks/litellm: coder-fast alias -> :8020 (mode: completion, /v1/completions).
- stacks/zed-fim-proxy (NEW): keyless /v1/completions front door on ana-docker
  :4141 for Zed (which can't send an auth header) — POST + path + model
  allowlist, injects a coder-fast-scoped virtual key -> LiteLLM :4000. Anon
  /ping liveness. Verified keyless FIM end-to-end.

Zed api_url = http://10.250.50.70:4141/v1, model coder-fast, prompt_format qwen.
Source-IP allowlist off pending the Mac's observed source IP.
2026-07-27 22:55:21 -07:00
vh 9e2f787567 fix(litellm): route kimi-k3 to the Kimi Code (coding) endpoint
The Heid panel plan uses Kimi's coding endpoint, not the general Moonshot API.
kimi-k3 now → openai/k3 @ https://api.kimi.com/coding/v1 (KIMI_CODE_API_KEY,
Vivace); the original general-endpoint entry is kept as kimi-k3-gen-api
(api.moonshot.ai, MOONSHOT_API_KEY). Both verified live through the gateway.
Same k3 constraints on both: temperature MUST be 1 (else 400), reasoning model
(reasoning_content vs content, needs adequate max_tokens).
2026-07-25 11:00:15 -07:00
vh edaa9a9c50 feat(litellm): front Kimi K3 (Moonshot) as a paid gateway passthrough
Adds model_name kimi-k3 → openai/kimi-k3 @ https://api.moonshot.ai/v1
(OpenAI-compatible), keyed by MOONSHOT_API_KEY (compose env + .env.example
placeholder; real key on server only). Verified live through the gateway.

Two Moonshot constraints captured in the config comment + pinned: K3 accepts
ONLY temperature=1 (else 400), and it is a reasoning model (CoT in
reasoning_content, answer in content — needs adequate max_tokens or content
returns empty). Model id confirmed via /v1/models.
2026-07-25 10:53:43 -07:00
vh a5dcad8bd3 feat(comfyui): switch allocator to cudaMallocAsync (A/B won, fixes LTX OOM)
Mirror comfy-dev's operator-run allocator A/B result off irv-ml1: drop
--disable-cuda-malloc (ComfyUI keeps CUDA's default async allocator) and
remove PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (native-allocator-only,
inert under cudaMallocAsync). The native+expandable_segments combo was
fragmenting/over-reserving (~45 GB allocated-but-unused) and OOMing the LTX-2.3
v1.5.0 LoRA stack at Gemma TE load; cudaMallocAsync packs tighter + returns
freed blocks so the job fits (stress test peaks ~82% VRAM, 0 OOM). The
shared-A6000 phantom-OOM that --disable-cuda-malloc guarded is gone since TTS
moved to the 3090 (2026-06-18).
2026-07-19 10:36:46 -07:00
vh 1ba6dc3257 docs(comfyui): bake RTX VSR provisioning into canonical README
Record the RTXVideoSuperResolution node clone + the nvidia-vfx pip install
(scoped --extra-index-url, uid 1000) in the stacks/comfyui runbook. Flag the
durability split: the node is persistent (basedir/custom_nodes) but nvidia-vfx
lives in the disposable venv (run/), so it must re-run after every fresh
bootstrap. Deliberately not a global PIP_EXTRA_INDEX_URL (torch-pin safety).
Closes the comfy-dev provisioning ticket.
2026-07-19 08:27:10 -07:00
vh 4bdf01001c docs(zonos-gateway): sync emotion-presets spec + memory (0.2.1 bake)
Mirror the canonical EMOTION-DIALS-SPEC.md from vh/zonos-gateway (now carries
the provisional per-voice emotion presets baked as gateway 0.2.1) and capture
the axes-sweep → bake arc in persistent memory.
2026-07-18 01:37:27 -07:00
vh 438cd35436 docs(zonos-gateway): sync mirror compose — host-managed voices bind-mount (Emmie)
Reflects the deployed change: ./voices:/app/voices:ro so voices are a filesystem
drop-in. Emmie voice added + committed to vh/zonos-gateway.
2026-07-17 22:54:34 -07:00
vh d725da0c90 docs(zonos-gateway): vh/zonos-gateway stood up in gitea (resolve not-in-gitea flag)
Created private vh/zonos-gateway on gitea, imported the previously-unversioned
~/zonos-gateway working tree (source + dials-first spec + voices). Updated the
stack README, spec §8, and the sister-repos table to point at the repo. Remaining
follow-up: CI + deploy key to wire the irv-ml1 deploy tree to the repo.
2026-07-17 18:49:09 -07:00
vh 0a9fb85a52 docs(zonos-gateway): dials-first emotion spec (operator ruling)
Canonical direction: emotion set by twisting raw dials per-utterance, not preset
selection. Presets demoted to optional examples. Spec covers the dial vocabulary
+ ranges, the emotion_cfg_scale 'deaf by 1.5' rule (NO cap — documented ceiling,
explicit over implicit), measured RTF cost, starting-point dial-sets, an LLM
client system-prompt snippet, and clone reference guidance (~15-24s, no
transcript). Follow-ups flagged: align dials.py cfg help/metadata, trim in-code
PRESETS pending usage check, stand up vh/zonos-gateway for version control.
2026-07-17 18:33:26 -07:00