Files
command-center/agent/managed-skills/frank-tts-repoint-voice-consumers/SKILL.md
T
Frank 8af40c402b chore(agent): commit the machine tier and the doctrine text the checkout was already running
Landing blocker for #61 and #63: the deployment checkout is 12 commits behind and
`merge --ff-only` refuses while tracked paths are dirty. Discard is not available here
(workspace policy: no `checkout`/`stash` on shared state), so the dirt is committed.

- `agent/APPEND_SYSTEM.md`: the point-of-use / one-work-log / PR-link doctrine, already
  byte-identical to `origin/main` (`cmp` against the trunk blob: no difference). No new
  prose — the commit exists so the trunk merge can proceed.
- `agent/config.yml` and `agent/managed-skills/**`: the machine-live tier the heartbeat
  owns (AGENTS.md § Live-sync tiers).

`ALLOW_SECRETS=1` was used, deliberately and after auditing the staged diff by hand: the
only credential-shaped strings in it are two copies of `AKIA0000000000000EXAMPLE` — the
inert all-zero AWS example id — and they appear inside the two skill pages that teach
that exact placeholder shape. This checkout is still running the pre-#61 hook; the merged
gate (value-scoped placeholder filter, `71601f7`) exempts this value, so the refusal
dies with this landing. No `--no-verify` was used: the gate ran and its verdict is
recorded here.

Refs #61
2026-10-21 01:44:51 -07:00

4.5 KiB

name, description
name description
frank-tts-repoint-voice-consumers Rotate/rename a frank-tts voice across all consumers (repo DEFAULT_VOICE, Open Notebook speaker profiles over REST, docs) after packs change.

When

frank-tts voice packs on /home/dank/voices (Dankclaw) changed — packs added, removed, or renamed — and old voice names (e.g. narrator1/2/3) no longer resolve. Consumers still sending a dead name now 400 with unknown voice 'x'; available voices: .... This is a maintenance sweep, NOT a bug; confirm with the operator whether the change is intentional before "fixing".

Facts that make this non-obvious

  • frank-tts (:7772) resolves a voice as a directory: <name>/reference.wav must exist. model field is ignored; the voice comes solely from voice/voice_id.
  • Live census: curl -s http://127.0.0.1:7772/v1/audio/voices (and /health returns the same list). This is the truth; the vault docs may be stale.
  • Two consumers carry voice names, in different stores:
    • repo: scripts/frank-tts.env DEFAULT_VOICE= — answers a voice-less request (the omp harness's normal shape). Deploy via git.
    • app-side: Open Notebook speaker profiles (SurrealDB) and OpenWebUI audio.tts voice — NOT repo files.

Step 1 — repo: DEFAULT_VOICE

Edit scripts/frank-tts.env DEFAULT_VOICE=<new real voice>. Trunk-based: this worktree can't git checkout -b (shared-workspace policy); commit on HEAD and git push origin HEAD:refs/heads/<branch>, open PR vs main. No bats test pins the DEFAULT_VOICE value (the tts bats use narrator1 as a self-contained fixture that builds its own reference.wav — mechanism test, leave it).

Step 2 — Open Notebook speaker profiles (REST, host-side, takes effect immediately)

Voice ID is free text passed to the TTS request untouched (no enum/whitelist). The active fleet profile is dank-duo (episode profile dank-daily → speaker_config). PW is the host OPEN_NOTEBOOK_PASSWORD.

ssh dankclaw bash -s <<'REMOTE'
PW=$(grep -m1 '^OPEN_NOTEBOOK_PASSWORD=' /home/dank/Containers/open-notebook/env | cut -d= -f2- | tr -d '"')
cfg=$(mktemp); chmod 600 "$cfg"; printf 'header = "Authorization: Bearer %s"\n' "$PW" > "$cfg"
base=http://127.0.0.1:5056/api/speaker-profiles
# census: name -> speaker/voice_id
curl -s -K "$cfg" "$base" | jq -c '.[]|{name,speakers:[.speakers[]|{name,voice_id}]}'
# repoint by NAME (GET-by-name returns full record incl id; PUT whole transformed object to /{id})
curl -s -K "$cfg" "$base/dank-duo" > /tmp/s.json
id=$(jq -r .id /tmp/s.json)
jq '(.speakers[].voice_id) |= ({"OLD1":"NEW1","OLD2":"NEW2"}[.] // .)' /tmp/s.json > /tmp/s2.json
curl -s -K "$cfg" -X PUT -H 'Content-Type: application/json' -d @/tmp/s2.json "$base/$id" -w ' HTTP %{http_code}\n'
curl -s -K "$cfg" "$base/dank-duo" | jq -c '[.speakers[]|{name,voice_id}]'   # verify
rm -f "$cfg" /tmp/s.json /tmp/s2.json
REMOTE
  • A duo has only 2 speakers — an extra voice is available-but-unassigned, not missing.
  • Upstream's seeded profiles (business_panel, solo_expert, tech_experts) use OpenAI names + voice_model:null; they aren't wired to frank-tts — leave them.

Step 3 — OpenWebUI (check before touching)

curl -s http://127.0.0.1:<webui-port>/api/config | jq '.audio.tts' (port from podman ps). If engine/openai.* are null, the active path is browser-kokoro (synthesizes in-browser) and the stored voice is dormant, not broken — flag, don't edit a live UI unasked.

Step 4 — docs (point of use, one owner)

Update the census + speaker-mapping lines in serverAppConfigs-docs/Dankclaw/Frank TTS.md and Open Notebook.md to the live packs. Leave DATED measurement logs (e.g. a 2026-10-19 /health probe) as history. Do NOT chase scripts/voice-packs.env / Voice Packs.md narrator builder defs — ask the operator whether those packs are being rebuilt or retired.

Verify (behavior, not config text)

Synthesize each new voice and expect 200 + distinct bytes:

curl -s -o /dev/null -w '%{http_code} %{size_download}B\n' -X POST http://127.0.0.1:7772/v1/audio/speech \
  -H 'Content-Type: application/json' -d '{"model":"voxcpm2","input":"test","voice":"<name>"}'

tests/run.sh EXIT=0 before commit. DEFAULT_VOICE only lands after merge + ./scripts/deploy.sh dankclaw --service frank-tts; run that from the dev main checkout (/home/blank/Projects/serverAppConfig), NOT the agent worktree (deploy.sh refuses a non-main branch and pushes main forward — it must run on the dev box, not the target).