Commit Graph

227 Commits

Author SHA1 Message Date
vh c3aae2e435 skaldsong: declare VibeVoice TTS env vars for issue #73 dual-engine reader 2026-05-27 20:41:41 -07:00
vh 4a4a6fc803 llama-swap: artemis-31b-v1i — add --reasoning on + deepseek format
Worldtree-dev's --reasoning-format gemma suggestion isn't supported in
the deployed llama.cpp build (accepts only none|deepseek|deepseek-legacy).
Falling back to deepseek, which also populates reasoning_content — the
field Worldtree's GemmaProvider fallback path checks. Verified via test
inference: 608 reasoning_content deltas + 199 content deltas + 0 raw
<|channel> marker leaks.
2026-05-27 00:06:54 -07:00
vh 7f918271ac llama-swap: add artemis-31b-v1i (BeaverAI Gemma 4 fine-tune, Q6_K) 2026-05-25 22:42:25 -07:00
vh 8db180885f skaldsong: drop PYTHONASYNCIODEBUG=1 — SSE-wedge diagnostic expired 2026-05-25 00:43:17 -07:00
vh 532356fcbe corviduo-dev: log Worldtree v0.25.2 config-as-bind-mount landing (#205) 2026-05-24 20:28:46 -07:00
vh 5528769211 dockge: parameterize stacks-root path + deploy on corviduo-dev
Made the host-stacks bind-mount path configurable via
DOCKGE_HOST_STACKS_ROOT (default /opt/docker, unchanged for the
existing five hosts). Override on corviduo-dev to /home/vh/docker
because that host's /opt/ is owned by deploy:deploy (Worldtree team)
and vh lacks passwordless sudo for the fleet-standard path — same
reasoning as the beszel + dozzle agent placement earlier today.

Deployed to corviduo-dev. Reachable at http://10.250.50.152:5001
(first probe 200 — Docker's port-mapping route through iptables
worked without firewall changes, unlike beszel's network_mode: host).

Scoped to PFI-managed stacks only (/home/vh/docker/compose/) — does
NOT see /opt/worldtree*/ deployments. Keeps the management boundary
clean: dockge can restart/recreate PFI's beszel+dozzle+itself but
not the Worldtree-team-owned containers.
2026-05-23 21:23:44 -07:00
vh 1cd79e8274 corviduo-dev: deploy beszel + dozzle monitoring agents
Operator-approved fleet monitoring extension. Both agents up + healthy:
- beszel-agent on host port 45876 (KEY-mode, hub at ana-docker:8090
  SSH-polls inbound; seeded with hub's ed25519 pubkey).
- dozzle-agent on host port 7007 (mTLS auto-generated; hub at
  ana-docker:8088 connects inbound).

Compose lives at /home/vh/docker/compose/{beszel,dozzle-agent}/ rather
than the fleet-standard /opt/docker/compose/{...}/ because corviduo-dev's
/opt/ is owned by deploy:deploy (Worldtree team) and vh lacks
passwordless sudo to create the fleet path. Functionally identical;
documented in servers/corviduo-dev/README.md so future infra-ops
sessions find them.

Created an empty traefik-net external docker network on corviduo-dev
as a side effect of dozzle-agent's compose (which declares it external).
Future PFI services landing here can reuse it.

Dozzle hub on ana-docker had 10.250.50.152:7007 appended to
DOZZLE_REMOTE_AGENT (a host-side change to a non-tracked .env, not
canonical). Beszel hub still needs corviduo-dev added via the UI's
"Add System" action — one-time operator step, flagged in the corviduo
README.
2026-05-23 18:13:59 -07:00
vh 92554221be skaldsong: enable PYTHONASYNCIODEBUG to surface orphaned-task warnings
Temporary diagnostic for the class of bug story 83ff386d47c6 hit
2026-05-23: POST /generation/start returned 202, then total silence
— no log, no DB state update, py-spy showed event loop idle with no
GenerationRunner frame anywhere. Strongly suggests a created_task()
result not held → GC'd → silent destroy.

PYTHONASYNCIODEBUG=1 emits "Task was destroyed but it is pending"
and "Task exception was never retrieved" warnings to stderr; that
should distinguish lost-task from cancelled-task on the next attempt.

Per skaldsong-dev's note, remove once they wire proper task-exception
capture upstream.

Diagnosis thread: althing 01KSBGKQBXA756JWW1KD4MPGXM
2026-05-23 16:04:55 -07:00
vh 307b01e8a1 snapshot: capture skaldsong env-var-name footgun (CD wiping state)
Same lesson family as the /app/web/dist mismatch — encoding
container-internal contract (paths OR env var names) in compose
needs to be verified against the Dockerfile + app, not against
design-doc shorthand. Wrong env var names silently no-op; app
falls back to Dockerfile defaults which orthogonally miss the
bind mount, and state goes to ephemeral layer until next recreate.
2026-05-20 21:57:51 -07:00
vh 52e98fa2d0 skaldsong: fix CD-wipes-state bug (env var names didn't match app)
The compose set SKALDSONG_DB_PATH + SKALDSONG_RUNS_DIR, but skaldsong's
app reads SKALDSONG_HOST_SQLITE_PATH + SKALDSONG_HOST_RUNS_ROOT (per
its Dockerfile ENV defaults). Our values were orthogonal — the app
fell back to Dockerfile defaults pointing at /app/data/... which is
NOT bind-mounted, so every --force-recreate wiped the SQLite DB +
runs/ tree along with the ephemeral container layer.

Surfaced by skaldsong-dev (althing thread 01KS4DPF6SXTBP4Q360JZVWPNT)
after the operator noticed stories vanishing on every deploy.

Confirmed on ana-docker: container had a 40KB skaldsong-ui.db sitting
in /app/data/, while /opt/docker/conf/skaldsong/db/ on the host was
empty. Rescued the live DB to the bind-mount target before recreate.

Fix: rename env vars to match what the app reads. Bind targets stay
at /app/state/{db,runs} (parent-dir mount for SQLite WAL+SHM).
2026-05-20 21:55:48 -07:00
vh 1a0059f589 snapshot: roll persistent-memory.md from 2026-05-17 → 2026-05-20
Captures the May 18-20 session arc:

- Skaldsong CD shipped on ana-docker:8300 via registry-pull pattern
  (differs from althing/asset-engine which build-on-host); three
  first-deploy footguns surfaced + canonical-patched.
- Worldtree CD disk-hygiene PR #184 (vh/Worldtree) — watermark gate,
  eager post-deploy SHA prune, set -e hardening in SSH blocks.
  Approved by worldtree-dev, pending merge.
- mead-hall Bifrost v0.3 wire-layer + data-flow end-to-end smoke
  green (task #32 closed) — resolves the "stalled mid-Worldtree"
  state in the prior snapshot.
- Volva systemd install — three-stage debug (PATH, ReadWritePaths,
  handle pinning); impersonation footgun caught via PID/journal
  cross-reference.
- Per-project user keys minted against personal Worldtree (:8081)
  for skaldsong, althing, mead-hall + skaldsong-prod. Personal
  becomes multi-consumer dev iteration instance; demo stays for
  isolation.

File now at 434 lines (over 300 soft cap); next /snapshot run will
surface archival candidates.
2026-05-20 21:51:36 -07:00
vh c6864996f0 skaldsong playbook: verify container is Up, not 'healthy' (was racing start_period)
The verify step ran 0.09s after `docker compose up -d --force-recreate`,
well before the container's 30s start_period elapsed — so 'healthy'
isn't yet in docker ps's Status string and the grep failed. False
negative; container was operationally up (verify 1/2 already confirmed
/health 200).

Greping for '^Up' instead catches the case verify 2/2 actually wants
to catch (container exited entirely) without racing the healthcheck.
2026-05-18 23:47:51 -07:00
vh 4b8dc9f7e1 skaldsong: fix first-deploy footguns (SPA path + cors_origins shape)
Two corrections surfaced by the first end-to-end deploy that didn't
land in the pre-flight align:

- SPA static assets are at /app/spa, not /app/web/dist (Dockerfile
  COPYs the SvelteKit build output flat into /app/spa, not into
  /app/spa/dist). Mismatch caused /health to 500 with
  "RuntimeError: File at path /app/web/dist/index.html does not
  exist."

- SKALDSONG_HOST_CORS_ORIGINS must be a JSON array literal in .env.
  Pydantic-settings parses complex-typed env vars via json.loads();
  bare URL string fails first-boot with SettingsError.

Container now reports Up (healthy) on ana-docker; /health 200.
2026-05-18 23:35:13 -07:00
vh 8a4276d6b5 skaldsong: align env-var contract with app reality (pre-first-deploy)
skaldsong-dev surfaced three contract corrections before the first
deploy:

- WORLDTREE_TOKEN (outbound HTTP Bearer) was missing — separate code
  path from SKALDSONG_BIFROST_JWT_KEY (inbound HS256 verify) but
  same secret value.
- WORLDTREE_BASE_URL replaces SKALDSONG_WORLDTREE_API_URL (the
  former is what the app actually reads).
- SKALDSONG_HOST_WIZARD_AGENT_ID was missing entirely — must pin to
  skaldsong:wizard-v2 to inherit the existing Worldtree agent slot;
  blank would burn another slot of the 50-per-key Heimdall quota.
2026-05-18 23:10:37 -07:00
vh 55e2e836a8 skaldsong: scaffold compose stack + deploy playbook for ana-docker
Registry-pull pattern matching Worldtree: CI on vh/skaldsong builds and
pushes gitea.phasefinal.com/vh/skaldsong:<sha>, this playbook pulls +
recreates. SHA-pin only per current preference; no :latest moving-tag
advance yet (revisit once /health exercises Worldtree + Kokoro reach).

Host port 8300 (host) → 8000 (container). Persistent state under
/opt/docker/conf/skaldsong/{db,runs}.

Bifrost endpoint URL 10.250.50.70:8300 will need a paired
BIFROST_CLIENT_ALLOWED_HOSTS update on corviduo-dev Worldtree at first
deploy.
2026-05-18 22:01:29 -07:00
vh 8f50d702d1 homepage: add Notes group to Main tab (4-column row) 2026-05-17 22:46:41 -07:00
vh 9e9bdf9810 deploy-stack: add -n to ssh calls to prevent stdin slurp in loops
Without -n, ssh inherits the surrounding loop's stdin and consumes
the heredoc that feeds $changed / $deleted, silently truncating the
diff output to the first file only.
2026-05-17 22:46:41 -07:00
vh 4b986f0b23 snapshot: roll persistent-memory.md from 2026-05-08 → 2026-05-17
Captures 9 days of multi-stack work:
  - asset-engine catalog (lifecycle blocks for orchestrator,
    defaults audit, voice probe of voxtral/qwen3-tts/kyutai-tts)
  - asset-engine stack scaffold + LAN-direct deploy
  - corviduo-dev inventory (Worldtree-team dev VM)
  - Worldtree outage recovery + :latest tag drift fix
  - vor key injection
  - vllm-qwen3 → vllm rename + vllm-reward (Skywork-Reward-V2 AWQ)
  - pull-hf-repo.yaml (model+dataset+space puller)
  - selene-1-mini-8b judge model in llama-swap
  - /tend-docs first pass (delete infinity, archive design-brief,
    fix pfi-pve VM list, drop stale postgres-rotate section)
  - althing-chamber stack (chamber + forseti + agent-runner + valkey
    across Phase 1/2/3.1)
  - Worldtree admin workflow shift (infra-ops permanent admin key
    replaces docker-as-root + bootstrap admin)
  - mead-hall Bifrost consumer registration
  - skaldsong CI bootstrap (3 captured gotchas)
  - Phase 3 + Phase 3.1 streaming activation

Sections grew: Tools and conventions (+sister-repo rows, admin auth
note, docker-as-root pattern), Recent decisions (+12), Tried and
abandoned (+5). Current state / in-flight rewritten to today's
posture (Worldtree handshake gap, hygiene-debt rotation list, prior
open items still active).

294 lines total; under the 300 soft cap. No archival this run.
2026-05-17 14:25:56 -07:00
vh 5704fbb08f althing-chamber: add valkey for Phase 3.1 cross-process streaming
Phase 3.1 closes the cross-process gap the Phase 3 smoke surfaced —
streaming events (msg_start/thinking/delta/complete/curated) flow
from agent-runner → chamber via valkey pub/sub rather than the
SQLite bridge (too high-volume + ephemeral for the DB).

New service: `althing-valkey` (stock `valkey/valkey:8-alpine`).
Internal-only — no exposed port, no volume. chamber + agent-runner
reach via docker DNS at `valkey:6379` on the compose default
network. healthcheck via `valkey-cli ping` (5s interval). chamber
+ agent-runner gain `depends_on: valkey: service_healthy` so the
bridge is up before either side starts publishing or subscribing.

Forseti unchanged — never publishes Phase 3 events.

Operational properties (per forseti's deployment notes):
  - Mixed-state safe at every step. Missing valkey.url config key
    → chamber + runner stay on v3.0 / Phase 2 equivalent paths.
  - Backward path is single config-key delete + restart.
  - streaming_enabled: true (set on agent-runner 2026-05-17) is
    unaffected by this change.

README's services table + playbook header + verify section all
extended to reflect the four-service shape. Forseti's contract
at vh/althing:docs/contracts/phase3_1_valkey_bridge.contract.md
carries the wire-protocol spec.
2026-05-17 08:36:46 -07:00
vh 59899e4e0f althing-chamber: add althing-agent-runner as third compose service
Phase 2 daemon added to the althing-chamber stack per forseti's request
(vh/althing@5cd088a..ad1d025). Polls floor_grants WHERE consumed_at IS
NULL AND agents.driver='worldtree', claims via atomic UPDATE, calls
Worldtree's conversation API, posts the response back through the bus
as a broadcast.

Shape matches the existing forseti daemon:
  - Same ${ALTHING_IMAGE} (the binary is already in [project.scripts]
    as of ad1d025)
  - command: ["althing-agent-runner"]
  - Same shared SQLite bind-mount at /app/data
  - No port, no healthcheck (CLI doesn't expose one; same liveness
    story as forseti)

Safe to enable preemptively per forseti — when no driver=worldtree
handles are declared in config, the runner sleeps at
poll_interval_seconds. Multi-instance safe via the atomic claim
primitive (no flock needed).

Compose top comment, README "Services in this stack" table, playbook
header + verify steps all extended to reflect the three-service
shape. Will land on ana-docker on vh/althing's next push (compose
deployed via the elway playbook's upload step; image already carries
the binary).
2026-05-16 20:01:57 -07:00
vh 91d5417b0b althing-chamber: scaffold deploy stack on ana-docker
Two-service compose (chamber + forseti sidecar daemon) sharing a single
SQLite store via bind-mount under /opt/docker/conf/althing-chamber/data.
eventbus.bridge_from_db is the cross-process glue — forseti's commits
reach chamber's SSE subscribers via the bridge.

Pattern matches task-board's build-on-host deploy:
  - elway playbook clones vh/althing into /opt/docker/build/
  - docker build -t althing-chamber:local . (no registry)
  - playbook uploads compose + seeds .env one-time, brings both
    services up, polls /health
  - Gitea Actions workflow lives in vh/althing; reference copy here.

Internal tooling — host port 7881 (chamber's default of 7878 collides
with task-board). LAN-direct, no Traefik. Container always listens on
8000 internally.

Scaffold will fail to bring the chamber container up healthy until
galdrabok-side commits land:
  - Dockerfile at vh/althing repo root (two-stage: uv-bookworm-slim
    build → python:3.12-slim runtime, locked per open_questions §2
    of the v1 contract).
  - GET /health endpoint on the chamber app (200, no DB read).
  - ALTHING_BIND / ALTHING_PORT env-var support in
    core.cli.chamber_serve / core.chamber.cli (env > config.yaml >
    defaults precedence).

Coordinated via althing thread 01KRMAK7RD7TP6C8DF4KXV31RT.
2026-05-14 15:54:19 -07:00
vh c27761f608 servers/pfi-pve: full VM/CT enumeration from live qm list
Previously a partial list "from vzdump logs + servers/ dirs". Replaced
with the full output of `qm list` on pfi-pve (2026-05-14), cross-
referenced against `servers/`. VMs that have a server dir are
annotated with the path; ones that don't are flagged "not yet in
`servers/` inventory" so the gap is discoverable.

Resolved:
- VM 100 = pbs-ana (was "VM (TBD)" in prior version)
- VM 106 = corviduo-dev (added 2026-05-12; was missing from the list)

Surfaced (new findings, not yet inventoried):
- VM 101 — PFI-ANA-DC (Active Directory domain controller)
- VM 103 — PFI-SlaveBot (purpose unclear from name alone)
- VM 104 — PFI-Mongo (MongoDB host, separate from pfi-postgres)

The three new-to-inventory VMs deserve `servers/<name>/` directories
with READMEs, but that's a follow-up — creating new inventory entries
is out of tend-docs scope.
2026-05-14 15:38:34 -07:00
vh 1f37857acc servers/pfi-postgres: drop stale "Known weak password (rotate)" section
Section claimed gitea + paperless-ng Postgres passwords were "currently
in use" with trivial values and that rotation was pending. Per
STATUS.md: "Rotate exposed secrets — done 2026-04-23. All six rotated:
vaultwarden/gitea/paperless-ng Postgres passwords (hardcoded
compose.yaml literals moved to gitignored .env files in the process)..."

Rotation happened; literals are no longer in compose.yaml; passwords are
no longer trivial. Removing the section rather than amending — once
fixed, there's no value in carrying a "we have weak passwords"
section that lies about the current state.

Surfaced by /tend-docs audit 2026-05-14.
2026-05-14 15:34:02 -07:00
vh 4e0059f388 servers/pfi-pve: pfi-postgres PGDATA is on local VM disk (not NFS)
VM 105's annotation said "PGDATA on NFS from ana-nas" — postgres
migrated off NFS to local VM disk on 2026-04-23 per STATUS.md ("DB data
on local disk, not NFS. pfi-postgres migrated 2026-04-23"). Updated to
reflect current reality with the migration date as the rot detector.

Still-TBD note for VM 33: `pbs-ana` is listed as "VM (TBD)" — was
deployed and has its own server dir, but its VM ID isn't recorded
here. `qm list | grep pbs` on pfi-pve would resolve, but my SSH to
pfi-pve as lkraven is currently password-required so I can't pull
this myself. Flag for next pfi-pve console session.

Surfaced by /tend-docs audit 2026-05-14.
2026-05-14 15:33:45 -07:00
vh 54d1e7137e docs: archive asset-engine design-brief.md
Moved docs/asset-engine/design-brief.md → docs/archive/asset-engine/design-brief.md
with a 12-line archival header pointing at the live implementation
artifacts (vh/asset-engine source, stacks/asset-engine/ deploy,
CATALOG-CONTRACT.md, services.yaml).

The brief explicitly framed itself as a pre-implementation handoff
("Hand this to a design agent before any pixels"). Implementation
shipped 2026-05-12; the brief's role is past. Kept for the design
rationale it carries (why Asset is first-class, why v1 is synchronous,
v2/v3 seam reasoning) — future contributors benefit from finding it
when wondering "why is it this way."

Surfaced by /tend-docs audit 2026-05-14.
2026-05-14 15:33:15 -07:00
vh 391244b18b docs: delete stacks/infinity (retired, replaced by vllm)
Stack was retired and replaced by the vllm stack (originally vllm-qwen3,
renamed 2026-05-13). Its README still framed it as a current solution
while ana-ml2's README + vllm's README both documented the retirement.
stacks/vllm/README.md "Migrating off Infinity" step 3 explicitly said
"Delete stacks/infinity/ from this workspace" — actioning that now.

No backwards-compat shims (PRACTICES §4): contract of a deleted system
has no historical value the next contributor needs; the replacement
path is documented in stacks/vllm/README.md.

Surfaced by /tend-docs audit 2026-05-14.
2026-05-14 15:32:43 -07:00
vh 7e7130172e vllm: rename stack from vllm-qwen3 → vllm + add Skywork reward classifier
Two related changes shipped together. The stack rename is independent
but adding `vllm-reward` to the existing `vllm-qwen3` would have made
that name actively misleading.

**Rename:** `stacks/vllm-qwen3/ → stacks/vllm/`. Updated all in-repo
references (README.md root, servers/ana-ml2/, stacks/llama-swap/,
configs/restic/ana-ml2/, docs/runbooks/disaster-recovery.md). Two
intentional history mentions retained (servers/ana-ml2 + stacks/vllm
README).

**Add `vllm-reward` service:** serves Skywork-Reward-V2-Llama-3.1-8B-AWQ
on port 8003. The AWQ output is a locally-quantized model (not from HF),
so bind-mounts `/tank/aimodels/llm:/local-models:ro` rather than the
shared HF cache. Model config.json declares LlamaForSequenceClassification
which vLLM's pooling runner picks up automatically — produces a single
reward score per input via /classify.

**Flag note:** the user's spec listed `--task classify`, but vLLM 0.19.1
deprecated --task in favor of --runner pooling (model architecture in
config.json drives the classification head). Compose uses --runner
pooling with a comment explaining the substitution.

**GPU memory:** no rebalance needed — production had already tuned
EMBED/RERANK down from 0.40 to 0.20 each (canonical .env.example now
matches reality). Adding REWARD at 0.30 totals 0.70, leaving ~14 GB
headroom on the 48 GB Ada.

**Server-side:** brought existing vllm-qwen3 down, mv'd
/opt/docker/compose/vllm-qwen3 → /opt/docker/compose/vllm, appended
REWARD_* lines to existing .env (preserving API_KEY/HF_TOKEN), deployed
new compose via scripts/deploy-stack.sh, brought all 3 services up.

**Smoke tests:**
- /health on 8001/8002/8003 → 200
- /v1/models on 8003 → lists Skywork/Skywork-Reward-V2-Llama-3.1-8B-AWQ
  with max_model_len 16384
- /classify with a sample conversation → returns LABEL_0 with prob 0.9999
  (single-output regression-style reward score, expected shape for a
  reward model)
2026-05-13 22:00:26 -07:00
vh 662a73ee0e rename: pull-hf-model.yaml → pull-hf-repo.yaml
Playbook handles models, datasets, and spaces (via --var repo_type=...)
since 3025d49 — the "-model" suffix was misleading. Renaming to match
actual scope.

Updates the single in-repo reference (changelog comment in
stacks/llama-swap/conf/config.yaml). config.yaml was scp'd to ana-ml2;
no docker compose restart needed (comment-only).
2026-05-13 18:33:22 -07:00
vh 3025d4930a pull-hf-model: support datasets and spaces, not just models
Adds optional repo_type var (default "model"; valid: model/dataset/space)
that's forwarded to `hf download --repo-type` and threaded through the
verify step (cache-dir prefix tracks repo_type: models--, datasets--,
spaces--).

The playbook was previously model-only because hf download defaults
repo_type=model — pulling a dataset surfaced as a 404 against the wrong
HF API path. Now generic.

Smoke-tested 2026-05-13:
  scripts/elway ana-ml2 --playbook playbooks/pull-hf-model.yaml \
    --var hf_repo=Skywork/Skywork-Reward-Preference-80K-v0.2 \
    --var repo_type=dataset
  → 200 MB parquet cached at
    /tank/aimodels/huggingface/hub/datasets--Skywork--Skywork-Reward-Preference-80K-v0.2/

Filename is misleading now (playbook handles more than models); leaving
the rename for a future cleanup since pull-hf-model.yaml is referenced
from the llama-swap config.yaml's 2026-05-13 changelog entry.
2026-05-13 16:25:40 -07:00
vh 53f00b232b llama-swap: add selene-1-mini-8b (Atla judge model, imatrix Q6_K)
AtlaAI's Selene-1-Mini judge model for evaluation/scoring tasks.
Llama 3.1 8B base, mradermacher imatrix-quantized Q6_K (~6.5GB,
quality-leaning quant). Apache-2.0. Per Atla cookbook these defaults
hit 84% on RAGTruth hallucination eval.

New 'JUDGE / EVAL MODELS' section between the dense chat models and
the embedding models — separate category from chat/reasoning since
the run-params shape is different (deterministic-leaning: temp 0.01,
top-p 1.0, no repeat penalty).

q8_0 KV cache to fit 32K ctx cleanly on the 3090 with headroom.

Pre-pulled into the shared HF cache via the new
playbooks/pull-hf-model.yaml playbook (canonical replacement for
ad-hoc huggingface_hub.snapshot_download calls; see CHANGELOG).

Smoke-tested 2026-05-13: GET /v1/models lists selene-1-mini-8b,
POST /v1/chat/completions returns expected output cleanly.
2026-05-13 16:19:01 -07:00
vh f9b933808c playbooks: pull-hf-model — generic HF→ana-ml2 cache puller
Codifies the previously-manual workflow described in
stacks/llama-swap/README.md: install hf CLI via pipx (one-time),
inject hf_transfer for fast multi-connection downloads,
`hf download` into the shared HF cache at /tank/aimodels/huggingface
with optional --include filter.

Model-format-agnostic by design — same playbook handles GGUFs for
llama-swap and safetensors for vLLM (both stacks read the same cache
dir via HF_HOME=/hfcache). Does NOT edit any consumer's config.yaml;
per-model run params (ctx-size, sampler defaults, quant choice,
chat template, etc.) stay human-curated.

Usage:
  scripts/elway ana-ml2 --playbook playbooks/pull-hf-model.yaml \
    --var hf_repo=<user>/<repo> \
    [--var allow_patterns='*Q6_K*']

Idempotent: hf CLI skips already-cached blobs; re-runs are
sub-second when the snapshot is already complete.

Smoke-tested 2026-05-13 against:
  - mradermacher/Selene-1-Mini-Llama-3.1-8B-GGUF (Q6_K, ~6.5 GB)
  - Skywork/Skywork-Reward-V2-Llama-3.1-8B (full safetensors, ~16 GB)
2026-05-13 16:00:45 -07:00
vh 9523343d3a servers/corviduo-dev: add Worldtree-team dev VM to inventory
Captures the access path + emergency-ops runbook surfaced during the
2026-05-12 demo outage (Z_AI_API_KEY / runtime validator / :latest tag
drift cascade).

- servers/corviduo-dev/{README.md,ssh-target,system-details.txt}
- CLAUDE.md servers table + placement-rules note

The "docker-as-root sudo bypass" pattern (vh's docker-group membership
→ effective root for any bind-mount-able operation) is the canonical
fix path when /opt/worldtree*/.env edits are needed and the deploy
user's sudo is out of reach. The README documents the pattern with
the appropriate "docker-group ≈ sudo" security warning so future
emergency-ops sessions don't have to re-derive it.

Ownership stance matches SF client hosts: PFI hosts + provides
emergency-ops backstop, Worldtree-team owns OS config / deploys /
backup decisions. Coordinate data-affecting work with the architect.
2026-05-12 23:36:11 -07:00
vh 108ac507ec catalog: voxtral/qwen3-tts/kyutai-tts defaults from live server probes
Booted each service on irv-ml1 to capture the wrapper's actual voice
exposure, then took them back down. Initial state restored.

**Voxtral (v1 → v2)** — 20 native presets from live /v1/audio/voices:
neutral_{female,male}, casual_{female,male}, cheerful_female, plus
8 language-code variants ({ar,de,es,fr,hi,it,nl,pt}_{female,male}).
vLLM-Omni does NOT translate OpenAI aliases; `alloy` would 400. Default
flipped to `neutral_female` (matches Mistral docs).

**Qwen3-TTS (v1 → v2)** — 15 voices from live /v1/voices, NOT the 17
the upstream blog cites: 9 Qwen presets (vivian/serena/uncle_fu/ryan/
aiden/ono_anna/sohee/eric/dylan) + 6 OpenAI-compat aliases (alloy/
echo/fable/nova/onyx/shimmer). Default `vivian` matches the wrapper's
OpenAPI default. Catalog previously stated clone-only — wrong; the
wrapper does ship presets, the upstream blog list just doesn't match
the deployed wrapper. Cloning still works alongside via clone:<name>.

**Kyutai-TTS (v1 → v2)** — NillPointer wrapper does NOT expose any voice
listing endpoint (/v1/audio/voices 404; only /health + POST /v1/audio/
speech are wired). Voices are filesystem-discovered. Catalog now drops
the broken source_url, switches voice field to free-text with default
`unmute-prod-website/default_voice.wav` (upstream's named default).
Description lists the on-disk categories with counts + license posture
(vctk = CC BY 4.0 commercial-safe; expresso = CC BY-NC research-only).
2026-05-12 22:51:33 -07:00
vh 51c77370b3 catalog: defaults audit — Kokoro/SAO/VibeVoice/Fish-s2/ace-step
Six default + description tightenings from upstream-source research:

- **Kokoro voice**: af_bella → af_heart. Per upstream VOICES.md, af_heart
  is the only A-rated voice; also the kokoro-fastapi container's own
  default. Applied to both kokoro (v2 → v3) and kokoro-captioned (v1 → v2).
- **SAO negative_prompt**: "Low quality." → "low quality, average quality".
  Per diffusers official docs Tips section — the lowercase comma-separated
  shape is the explicit recommendation; the period form was a code-example
  string, never a documented default. SAO bumped v1 → v2.
- **SAO prompt description**: added the model card's canonical examples
  ("128 BPM tech house drum loop", "the sound of a hammer hitting a wooden
  surface") plus the Tips advice on descriptive prompts.
- **VibeVoice voice**: en-Carter_man → en-Alice_woman. Per upstream model
  card, Alice is the documented default. Description now flags the
  Alice-injects-BGM-for-intros foot-gun + the `_bgm`-suffix meaning.
  VibeVoice bumped v2 → v3.
- **Fish-s2 text description**: added multi-tag placeholder example
  combining emotion + physical tags, per upstream best-practice docs
  (physical tags "feel flat without emotional context").
- **ace-step prompt description**: added the upstream Gradio UI's
  pre-filled tag string as the canonical prompt-shape example.

Surfaced separately to lkraven (NOT applied here, need decisions):
- Voxtral voice "alloy" may need to become a native preset like
  "neutral_female" — depends on whether vLLM-Omni translates OpenAI
  aliases.
- Qwen3-TTS — research found 17 presets (Cherry, Ethan, ...) that the
  catalog currently says don't exist. Catalog says clone-only; needs
  wrapper-level verification before adding.
- Kyutai-TTS — service down, can't probe /v1/audio/voices. Default
  remains undefaulted.
2026-05-12 22:29:22 -07:00
vh 079c7b15e3 asset-engine + irv-ml1: scaffold orchestrator wiring
Three changes prepping infra for asset_engine's orchestration feature
(SSH-driven bring-up / bring-down of irv-ml1 inference services with
per-device VRAM gating, contract in vh/asset-engine commit 5a36f8c):

1. asset-engine compose + .env.example + playbook gain a read-only
   bind-mount for /app/runtime/ssh — the dedicated ed25519 keypair
   (generated on ana-docker, not in the repo) plus a pinned known_hosts
   for irv-ml1's host fingerprint. Env vars SSH_KEY_PATH and
   SSH_KNOWN_HOSTS are exposed for the app to consume.

2. docs/asset-engine/services.yaml gains a `lifecycle: { stack, vram_gb,
   gpu_device_id }` block on each of 12 orchestratable irv-ml1 services
   (kokoro, chatterbox, index-tts, qwen3-tts, cosyvoice, fish-s2,
   kyutai-tts, vibevoice, voxtral, parakeet, stable-audio-open, ace-step).
   VRAM numbers are estimates from model footprint at fp16 — tune from
   real nvidia-smi measurements once the gate is live. comfyui and
   kokoro-captioned are deliberately excluded (variable-VRAM and
   shared-container respectively).

3. servers/irv-ml1/README.md docker-stacks table now lists all 13
   inference stacks (was only dockge + agents + comfyui) with port +
   GPU pinning columns.

Pubkey deployed to ~lkraven/.ssh/authorized_keys on irv-ml1;
end-to-end SSH from ana-docker → irv-ml1 verified with strict
host-key checking.
2026-05-12 13:24:07 -07:00
vh ddf20abc7e vor: pass VOR_WORLDTREE_KEY through to the container
Adds VOR_WORLDTREE_KEY + VOR_WORLDTREE_BASE + VOR_WORLDTREE_MODEL to vor's
compose environment with sane defaults. Empty key falls back to the
in-process MockWorldtree (the /mockup/ surface returns canned fixtures);
a real key issued by architect routes LLM calls at the demo Saga instance.

Key itself lives in ana-docker:/opt/docker/compose/vor/.env (not in the
repo).
2026-05-12 00:00:31 -07:00
vh 44086248f6 asset-engine: drop traefik wiring, LAN-direct only
Internal tooling — accessed at http://10.250.50.70:8200, not through
Traefik. Removes the unused traefik labels (router rule, TLS, crowdsec
middleware, loadbalancer port) and the traefik-net network membership;
homepage.href now points at host:port for direct discovery, matching
task-board's pattern. Playbook verify drops the traefik-net membership
check.
2026-05-11 22:01:44 -07:00
vh 116ed15875 asset-engine: scaffold deploy stack on ana-docker
Mirrors task-board's build-on-host pattern: elway playbook clones
vh/asset-engine into /opt/docker/build/, docker build, install compose +
seed .env, up -d, verify /health. No registry.

Internal-only tool — LAN port 8200 (bind 0.0.0.0) is primary; Traefik
labels additionally route asset-engine.phasefinal.com with TLS via the
anaprod cert resolver. DB and outputs are separate bind-mounts under
/opt/docker/conf/asset-engine/ so outputs/ can move volumes later
without touching DB state. INFERENCE_HOST defaults to 10.100.79.3
(irv-ml1 over WG). OIDC env seam is pre-allocated empty for v2.
2026-05-11 21:43:41 -07:00
vh f0d0d758d2 catalog: SAO declare seed_field for seed_surface
asset-engine's seed_surface contract needs reproducibility.seed_field to name
which CatalogField carries the seed so the UI can render a Roll button and the
server can fill empty seeds before persistence. SAO's seed field is type=number,
satisfying CatalogService._validate_seed_field.

ace-step is intentionally not declared here — actual_seeds is type=json (a list)
with an upstream reproducibility gap; the planned fix surfaces resolved seeds via
response header, pending a separate contract.
2026-05-11 21:17:06 -07:00
vh 95f24573e4 catalog: status: down for the 6 unreachable services; StatusT extended
Per althing thread 01KRCNSF0V5NDCKB34H663MXHS — the catalog declared
14 services but 6 of them aren't running on irv-ml1 (chatterbox,
index-tts, qwen3-tts, cosyvoice, voxtral, kyutai-tts; missing from
docker ps entirely). Without action, the asset-engine UI would
declare them as available and consumers would hit unreachable
endpoints.

asset-engine consumer chose option (1) of three I sketched: extend
StatusT with `down` and treat it identically to `catalog-deferred`
in the picker (greyed, non-clickable). Lightweight, declarative, no
runtime health-check machinery, easy to revert when services
return.

Changes:

- StatusT enum (in asset_engine/catalog.py — committed there
  separately) extended from
    Literal["ready", "catalog-deferred", "experimental"]
  to
    Literal["ready", "catalog-deferred", "experimental", "down"]
- 6 services flipped to status: down.
- CATALOG-CONTRACT.md: replaced the bare-enum status row with a
  four-row sub-table that names each value's meaning AND its picker
  behavior. `down` and `catalog-deferred` get the same UI treatment
  but the tooltip text differentiates ("Catalog-deferred" vs
  "Service down — temporarily unreachable on irv-ml1") so the
  semantic distinction (design state vs fleet-ops state) is
  preserved.
- CATALOG-CONTRACT.md versioning policy table: new row codifying
  "extending an existing enum (StatusT, FieldTypeT, ResponseTypeT,
  CategoryT) with a non-conflicting value, with the consumer
  updated in the same coordinated change" → no catalog_version
  bump. Explicit rule for future enum extensions.
- JSON Schema regenerated.

catalog_version stays at 1.

Operational note (not catalog-side): the down services likely got
reaped 13+ days ago per the docker timestamps when other unrelated
work was done on irv-ml1. Bringing them back is a deploy task
outside this commit's scope. Flip status: down → ready in this file
once each one's confirmed running.
2026-05-11 16:31:53 -07:00
vh a0d09edc42 catalog: vibevoice v1 -> v2 — fix model options, voice list, response_format enum
Sweep round caught vibevoice catalog drift in three dimensions; all
verified against the live OpenAPI + endpoint exercise, NOT against
documentation (which is what produced the bad values originally).

model:
  was: options=[vibevoice], default=vibevoice
  now: options=[tts-1, tts-1-hd, vibevoice], default=tts-1
  why: the wrapper accepts all three (OpenAI-compat aliases all map
  to VibeVoice internally per upstream README); wire default is tts-1
  per /openapi.json. Catalog over-constrained users to a single value.

voice:
  was: default=Carter; description listed [Carter, Davis, Emma, Frank,
       Grace, Mike, Samuel] as built-ins
  now: default=en-Carter_man; options enumerated:
       OpenAI: alloy, echo, fable, onyx, nova, shimmer
       VibeVoice: en-Alice_woman, en-Carter_man, en-Frank_man,
                  en-Mary_woman_bgm, en-Maya_woman, in-Samuel_man,
                  zh-Anchen_man_bgm, zh-Bowen_man, zh-Xinran_woman
  why: discovered by hitting the endpoint with the catalog's claimed
  "Carter" — wrapper returned 400 with the actual valid list inline
  in the error body. The previous catalog values were fabrications,
  not derived from any real source.

response_format:
  was: options=[wav, mp3]
  now: options=[wav, mp3, opus, flac, pcm]; default mp3 (was wav)
  why: probed all 7 plausible formats; 5 return audio (200), aac and
  m4a return 500. Catalog was over-restrictive; an earlier sweep
  draft over-claimed [wav, mp3, opus, aac, flac, pcm, m4a] from
  documentation that I refused to apply unverified. Now matches the
  empirically-confirmed set.

Bumped vibevoice version 1 -> 2. catalog_version stays at 1.

Lesson reinforced: the only source-of-truth for catalog values is
the live wire. /openapi.json doesn't enumerate enums (returns bare
"string"); error responses from the endpoint with bad inputs are
the most reliable enum-discovery mechanism.
2026-05-11 16:25:37 -07:00
vh f020049769 catalog: ace-step v5 — defaults audit against upstream Gradio UI
asset_engine consumer audited the entire ace-step entry's defaults
and slider ranges against acestep/ui/components.py (althing thread
01KRCN0SHP9YJGQD58EE95DC5P). The catalog had been authored from
documentation rather than from source; ten defaults were wrong and
several slider ranges were either too narrow or impractically wide.

Defaults changed (catalog -> upstream-authoritative):
  infer_step                 20    -> 60
  guidance_scale             7.5   -> 15.0
  cfg_type                   cfg   -> apg
  omega_scale                0.5   -> 10.0
  guidance_interval          0.0   -> 0.5
  guidance_interval_decay    1.0   -> 0.0
  min_guidance_scale         1.0   -> 3.0
  use_erg_tag                false -> true
  use_erg_diffusion          false -> true
  actual_seeds               [42]  -> []   (random per call)

Slider ranges adopted from upstream where reasonable; bounded
locally where upstream's range is so wide it's unusable as a UI
slider:
  guidance_scale         [1.0, 15.0]   -> [0.0, 30.0]   (upstream)
  guidance_scale_text    [0.0, 15.0]   -> [0.0, 10.0]   (upstream)
  guidance_scale_lyric   [0.0, 15.0]   -> [0.0, 10.0]   (upstream)
  lora_weight            [0.0, 2.0]    -> [-3.0, 3.0]   (upstream)
  audio_duration         [5.0, 600.0]  -> [5.0, 240.0]  (upstream max)
  omega_scale            [0.0, 1.0]    -> [-10.0, 30.0] (UI bound; upstream is [-100, 100])
  min_guidance_scale     [0.0, 10.0]   -> [0.0, 20.0]   (UI bound; upstream is [0, 200])

Verified empty-string actual_seeds path against the live pipeline
source: pipeline_ace_step.py:set_seeds() falls through to
torch.randint when manual_seeds is "" (string, no comma, not all
digits). Smoked end-to-end: HTTP 200 in 11s, real WAV bytes back.

Reproducibility gap honestly documented in the entry's
reproducibility.notes and the actual_seeds field description: with
the new default `actual_seeds: []`, the wrapper rolls a random seed
inside the pipeline but doesn't capture or surface the chosen seed
back through the response. Default-defaulted assets cannot be
regenerated bit-exact; users requiring reproducibility must set
actual_seeds explicitly. Wrapper enhancement to surface the chosen
seed via X-Actual-Seeds header + a CatalogResponse.header_accessories
schema field is the planned fix.

ace-step bumped version 4 -> 5. catalog_version stays at 1 (no
schema changes).

Also added a "source-of-truth precedence" subsection to
CATALOG-CONTRACT.md's service-authoring notes, codifying the
read-order (Pydantic model > handler/pipeline code > Gradio UI >
README). Three ace-step bugs in three rounds (missing field, wrong
enums, stranded bytes, wrong defaults — really four) all share the
same root cause: catalog authored from doc surfaces that lie by
omission.
2026-05-11 16:17:25 -07:00
vh f8ecc6c047 ace-step: stream audio bytes inline; catalog v3 → v4
The pre-fix wrapper at stacks/ace-step/infer-api.py returned a JSON
{output_path: "..."} reference to a file written inside the
container at /app/outputs/. That path was unreachable from outside
the container — every consumer got 134 bytes of JSON-pretending-to-
be-WAV instead of audio. Surfaced by the asset_engine consumer's
end-to-end smoke (althing thread 01KRCJF7NGMXYE9F62Q1A6KFD4 msg 5);
my own earlier smoke missed it because I checked HTTP=200 and stopped
reading instead of inspecting the response body.

Wrapper now reads back the file the pipeline writes and streams the
bytes via fastapi.responses.Response with media_type set from the
audio_format request field (audio/wav | audio/mpeg | audio/flac).
The in-container path is exposed via X-Output-Path header for log
correlation but is no longer load-bearing.

Verified end-to-end against live ace-step on irv-ml1:
  POST /generate  ->  HTTP 200 in 80s
  content-type: audio/wav
  content-length: 945226
  x-output-path: /app/outputs/output_cfe87d1d....wav
  $ file response.wav
  RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit,
    stereo 48000 Hz

Catalog: ace-step bumped version 3 -> 4. Dropped
response.output_field (no longer applicable). reproducibility.notes
expanded to record both the v2 18-arg-tuple fix and this v4
inline-streaming change so the history is auditable from the
catalog itself.

Stale ACEStepOutput Pydantic model left in infer-api.py for now —
unused but small; future cleanup.
2026-05-11 15:48:33 -07:00
vh 7d0a9fa09b catalog: ace-step v3 — fix scheduler_type and cfg_type enum values
Smoke testing in the asset_engine consumer surfaced an
UnboundLocalError 500 from ace-step (althing thread
01KRCJF7NGMXYE9F62Q1A6KFD4 msg 3). Root cause: this catalog had
invented enum values for scheduler_type and cfg_type that don't
exist in the upstream pipeline.

Read pipeline_ace_step.py inside the running container:

  scheduler_type dispatch:
    if  == "euler":    scheduler = FlowMatchEulerDiscreteScheduler(...)
    elif== "heun":     scheduler = FlowMatchHeunDiscreteScheduler(...)
    elif== "pingpong": scheduler = FlowMatchPingPongScheduler(...)
    # no else  -> "linear" / "squared" / "sqrt" leave scheduler unbound

  cfg_type dispatch:
    accepts: apg | cfg | cfg_star

Catalog had:
  scheduler_type: [linear, squared, sqrt] / default linear   <- all invalid
  cfg_type:       [none, cfg, cfg_rw]     / default cfg       <- only cfg works

Fixed:
  scheduler_type: [euler, heun, pingpong] / default euler
  cfg_type:       [apg, cfg, cfg_star]    / default cfg

Bumped ace-step version 2 -> 3. Existing assets generated under v2
with scheduler_type=linear cannot reproduce (the value is now invalid);
v2 assets with the accidentally-valid cfg_type=cfg + a corrected
scheduler can be regenerated under v3 by mapping linear -> euler.

catalog_version stays at 1 (no schema change).

Verified end-to-end against live ace-step on irv-ml1:
  POST /generate { scheduler_type: euler, cfg_type: cfg, ... }
  -> 200, output_path returned, ~8s wall time

Lesson: OpenAPI introspection isn't enough for accurate catalog
authoring. Upstream OpenAPI returns bare `string` for both fields.
Reading the actual dispatch code is the only way to capture the
allowed values. Will sweep the other 11 service entries against
their implementations before P2 (scale to all services) lands.
2026-05-11 15:41:03 -07:00
vh 52803d87f8 catalog-contract: section_groups + Field.section for progressive disclosure; ace-step v2
asset_engine consumer (althing thread 01KRCJF7NGMXYE9F62Q1A6KFD4)
needed structure for ace-step's 27-field form. Two additive Pydantic
changes — backward-compatible, no catalog_version bump per the
policy table:

  - CatalogField.section: str | None = None
  - CatalogService.section_groups: list[CatalogSectionGroup] = []
  - new CatalogSectionGroup model: {id, label, hint?}

Validator: every Field.section value must reference a declared
section_groups[].id within the same service; section_groups[].id
values are unique. CATALOG-CONTRACT.md updated with both the new
service-fields row and a versioning-policy row covering
"add optional Field/Service keys -> no bump."

ace-step entry rewritten to use the new schema:
  - bumped version 1 -> 2
  - declared 6 section groups (basic / generation / conditioning /
    a2a / lora / output) with hints
  - tagged every field with a section
  - added previously-missing checkpoint_path (required: true,
    default: "/app/checkpoints" — the container's mount path).
    Wrapper-side cleanup (default in infer-api.py) queued as
    follow-up.
  - changed lyrics from optional: true -> required: true with
    default "" to match upstream's `lyrics: str` shape (empty
    string satisfies it).

JSON Schema regenerated.

Pydantic-model side of this change lives in asset_engine at
src/asset_engine/catalog.py — committed there separately.
2026-05-11 15:33:08 -07:00
vh 4089990c17 docs/asset-engine: kokoro-captioned status experimental → ready
Consumer-side renderer for the JSON-envelope + timestamps shape
shipped (althing thread 01KRCF4W66X3, msg 5). Smoke + regression
clean. Per the contract on the entry's notes block, flipping to
ready now that the renderer is in place.
2026-05-11 15:01:15 -07:00
vh d3faeb0314 catalog-contract: add response-decomposition fields (audio_field, timestamps_field, audio_format_field)
asset_engine consumer needed to render kokoro-captioned, whose wire
shape is a JSON envelope carrying base64-encoded audio plus a
structured timestamps array. Modeling it as response.type=json
would force either a per-service-id renderer (forbidden by
brief §1.7) or extending the closed response-type vocabulary
(forbidden by brief §2.2 without a coordinated bump).

Resolution (per althing thread 01KRCF4W66X3): keep response.type
closed at the existing six values and decompose at the response
*field* level instead — the same flexibility seam already used by
mime / mime_from_field / output_field. Adds three optional keys:

  - audio_field: JSON key holding base64-encoded audio bytes
  - audio_format_field: JSON key holding the decoded audio MIME
  - timestamps_field: JSON key holding a structured timestamps array
    (independent of type, declared by any service emitting time-
    aligned markers)

Validators in CatalogResponse enforce sane combinations:
  - audio_field requires response.type=audio
  - audio_field forbids mime_from_field
  - audio_format_field requires audio_field

This is additive and backward-compatible — no catalog_version bump,
existing services parse unchanged. CATALOG-CONTRACT.md updated with
the new rows in the response-field table and a versioning-policy
row codifying that adding optional keys to response: doesn't bump.

kokoro-captioned re-shaped to use the new schema:
  response:
    type: audio
    audio_field: audio
    audio_format_field: audio_format
    timestamps_field: timestamps
And marked status: experimental until the asset_engine consumer's
audio-with-timestamps renderer ships.

JSON Schema regenerated to reflect the new Pydantic shape.

Pydantic-model side of this change lives in the asset_engine repo
at src/asset_engine/catalog.py — committed there separately.
2026-05-11 14:55:15 -07:00
vh 44c565ac77 docs/asset-engine: kokoro v2 + new kokoro-captioned entry
Per a request from the asset_engine consumer (althing thread
01KRCF4W66X3N24B01FF2Y7V3D), and verified against the live kokoro
OpenAPI + exercised endpoints:

* kokoro: version 1 → 2; adds three fields surfaced by the upstream
  schema but not previously declared:
    - speed (slider 0.25–4.0, default 1.0)
    - volume_multiplier (slider 0.5–2.0, default 1.0; UI-bounded
      since upstream is unbounded — noted in description)
    - lang_code (text, optional override of the voice-name-derived
      language hint)

* kokoro-captioned: new service entry wrapping
  /dev/captioned_speech. Same model + image as kokoro proper but
  separate catalog entry because the response shape is structured
  JSON (audio inline as base64 + word-level timestamps), not raw
  audio bytes. Verified shape captured in reproducibility.notes
  so future consumers don't have to re-discover it. response.type
  = json (consumer renders custom: player + subtitle overlay).

* reproducibility_audit: row added for kokoro-captioned.

Deferred (separate from this commit):
- kokoro-blend-voice. /v1/audio/voices/combine returns 403 on the
  default config (allow_local_voice_saving=False); even with the
  flag flipped it writes to a temp dir, not /worktank/kokoro/user_voices.
  The persistent blend mechanism in this fleet is
  playbooks/blend-kokoro-voice.yaml. Ad-hoc blending already works
  through /v1/audio/speech via the inline syntax voice="a(w)+b(w)";
  consumer can surface that as a UI affordance without any
  catalog change.

catalog_version stays at 1 (no field-type vocabulary changes).
JSON Schema regeneration produced byte-identical output.
2026-05-11 14:38:05 -07:00
vh 0157066d6e docs/asset-engine: promote services.yaml to first-class contract
Adds the supporting infra around the service catalog now that it
has external consumers (the asset_engine UI being the first; CLIs,
monitoring, other services may follow):

- CATALOG-CONTRACT.md: the consumer-facing contract. Defines
  versioning policy (catalog_version vs per-service version),
  closed field-type and response-type vocabularies, recommended
  vendor+drift-check sync workflow, known-consumers list, service
  authoring notes.
- services.schema.json: JSON Schema (draft 2020-12) for the
  catalog. Generated from the Pydantic model in
  ~/development/asset_engine/src/asset_engine/catalog.py via
  `uv run scripts/dump_schema.py --publish`. Lets non-Python
  consumers validate against the same shape.
- services.yaml: adds catalog_version: 1 at the root and reframes
  the file's header to call out its first-class-contract status.
  Quotes a vibevoice label that contained an unescaped colon
  (caught by the asset_engine's strict YAML parser on first sync).
2026-05-11 08:57:35 -07:00
vh 8d8d45b7ca docs/asset-engine: catalog + UI design brief
services.yaml: form-generator contract for the forthcoming
asset-generation UI. 13 inference services on irv-ml1 (TTS, ASR,
SFX, music) catalogued with field schemas extracted from Pydantic
models, response types, reproducibility audit, and license
warnings. ComfyUI flagged catalog-deferred (workflow-DAG API
doesn't fit a form-based UI without a per-asset-type wrapper).

design-brief.md: the prompt to give a frontend-design agent before
any pixels. Locks in the data-model decisions whose later cost is
asymmetric (asset-as-first-class entity, content-addressed output
storage, reproducibility hard requirement, job table, auth as a
no-op DI seam, API surface ≠ UI surface, schema versioning,
tags/collections plumbed in v1 with no UI). Defines a closed
field-type vocabulary (8 types) and response-renderer vocabulary
(6 types) — agent isn't allowed to extend them. Pre-decides the
required UI surfaces; leaves IA, library-nav pattern, long-job
UX, and big-form ergonomics open for the agent to opine on.
2026-05-10 19:20:58 -07:00