Skip to content

fix(pulse): VoiceServer reads the DA voice from LIFEOS_CONFIG.toml - #2107

Open
pai-scaffolde wants to merge 1 commit into
danielmiessler:mainfrom
pai-scaffolde:fix/f-voice-reads-lifeos-config
Open

fix(pulse): VoiceServer reads the DA voice from LIFEOS_CONFIG.toml#2107
pai-scaffolde wants to merge 1 commit into
danielmiessler:mainfrom
pai-scaffolde:fix/f-voice-reads-lifeos-config

Conversation

@pai-scaffolde

Copy link
Copy Markdown

Reproduced on a fresh LifeOS 7.40.4 install (macOS, Claude Code in the Claude desktop app); the fix was applied to that install and run there before filing. Precedent: PR #1781 made hooks/lib/identity.ts read the config first; voice.ts was left on the mirror.

Observed

After /interview wrote a custom voice_id into USER/CONFIG/LIFEOS_CONFIG.toml [da.voices.main], Pulse kept speaking with the template default voice (Rachel, 21m00Tcm4TlvDq8ikWAM). settings.json had no daidentity.voices.main.voiceId of its own; nothing in the shipped tree writes that block.

Root cause

LIFEOS/PULSE/VoiceServer/voice.ts resolves the main voice from settings.json daidentity.voices only (loadVoiceConfigFromSettings() at startup and a live read on /notify/personality). Per SystemUserBoundary.md and settings.enhancements.json ("LIFEOS_CONFIG.toml is the canonical config source; settings.json carries a runtime mirror of daidentity for hooks") the toml is canonical and the Interview writes only the toml. No shipped tool projects the mirror, so the VoiceServer never sees the chosen voice.

Fix

Read [da.voices.main] through LIFEOS/TOOLS/LifeosConfig.ts (Bun-native TOML, no new dependency) — the same loader identity.ts uses:

  • configMainVoice() maps LifeosVoiceSettings to the module's VoiceEntry (same defaults the settings.json mapper applies) and returns null when no config loads (LifeosConfig throws on a missing file, empty [da].name, or missing voice_id — that catch is the fallback path).
  • loadVoiceConfig() = settings.json mirror, then the config's main voice overriding voices.main / voicesByVoiceId / defaultVoiceId when present. startVoice() calls it.
  • The /notify/personality live read tries the config first, then the mirror as before.

Why not have the installer write the mirror instead: that keeps two writers for one value and leaves this reader inconsistent with the ones that already went to the config (#1781); the doctrine is that system code reads identity through loadLifeosConfig().

How tested

Probe (startVoice({enabled:true}) then voiceHealth().default_voice_id), temp HOME whose settings.json mirror carries Rachel, LIFEOS_CONFIG_PATH pointing at a temp toml with a distinct id:

toml present toml absent
unpatched 21m00Tcm4TlvDq8ikWAM (mirror) 21m00Tcm4TlvDq8ikWAM
patched AAAAAAAAAAAAAAAAAAAA (config) 21m00Tcm4TlvDq8ikWAM (mirror fallback)

Transpiles (bun build --target=bun --no-bundle). On the live install, curl localhost:31337/voice/health | jq .default_voice_id returns the toml's voice after a Pulse restart.

🤖 Generated with Claude Code

voice.ts resolved the main voice from settings.json daidentity.voices.main
only. That block is a runtime mirror of LIFEOS_CONFIG.toml [da.voices.main]
(the canonical source per SystemUserBoundary.md, written by the Interview),
and nothing in the shipped tree writes the mirror, so every fresh install
kept speaking the template default (Rachel) whatever voice the principal
chose. hooks/lib/identity.ts already reads the config first (PR danielmiessler#1781);
VoiceServer was the last reader on the mirror.

Read [da.voices.main] through LifeosConfig.ts (Bun-native TOML, no new
dependency) at startup and on the /notify/personality live read; fall
back to the settings.json mirror only when no config loads. Verified with
a temp HOME: config voice_id wins when the toml exists, the mirror's id
is used when it does not; before the change the mirror's id won in both
cases.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant