service_voice.nu — hero_voice server + UI lifecycle module #92
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_skills#92
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Child of #75.
Objective
Add
tools/modules/services/service_voice.nuimplementinginstall | start | stop | statusfor the hero_voice service (speech-to-text / text-to-speech).Scope
ssh://git@forge.ourworld.tf/lhumina_code/hero_voice.gitbuildenv.sh):hero_voice,hero_voice_server,hero_voice_uihero_voice_server,hero_voice_uilhumina_code/hero_zero/services/hero_voice.toml$HERO_SOCKET_DIR/hero_voice/rpc.sock,$HERO_SOCKET_DIR/hero_voice/ui.sock(confirm in spec)RUST_LOG=infoonly.RUST_LOG=info+HERO_VOICE_STT_LOCAL=true(TOML[ui.env]). Pass verbatim — spec should grep to confirm consumer is inhero_voice_ui/.Cargo.toml).--rootflag supported but optional; user-level default.Acceptance criteria
use services/mod.nu *makesservice_voiceavailable.service_voice install [--root] [--update] [--reset]clones + builds all 3 binaries; short-circuits when binaries already present (viasvc_bins_ok).service_voice start [--reset] [--root] [--update]registers both actions + the service, starts, prints summary. Idempotent without--reset.service_voice status [--root]reports state.service_voice stop [--root]cleanly unregisters.HERO_VOICE_STT_LOCAL=true.Template
service_indexer.nu(merged recently — the current canonical minimal two-binary module, ~200 lines, usessvc_bins_ok+--reseton install). Copy-rename with these hero_voice deltas:HERO_VOICE_STT_LOCAL: "true"on top ofRUST_LOG."Hero Voice — speech-to-text and text-to-speech server and UI".Spec should verify via
main.rsinspection:servesubcommand on either daemon.HERO_VOICE_STT_LOCALis actually consumed by the UI binary (it's declared in the TOML's[ui.env]).Implementation Spec for Issue #92
Objective
Add a
service_voice.nulifecycle module mirroringservice_indexer.nuso Hero Voice (server + UI) can be installed, started, stopped, and status-checked underhero_proc.Requirements
tools/modules/services/service_voice.nuexposinginstall,start,stop,status.hero_voice_server,hero_voice_ui) and one service (hero_voice) withhero_proc.HERO_VOICE_STT_LOCAL = "true"on top of the baselineRUST_LOG = "info". Server action sets onlyRUST_LOG = "info".lib.nuhelpers (svc_bin,svc_sock_base,svc_bins_ok,svc_cargo_install,svc_require_sudo,svc_require_proc,svc_need_sudo,svc_proc_healthy,svc_update). Do not modifylib.nu.tools/modules/services/mod.nuviaexport use service_voice.nu. Rebase againstorigin/developmentbefore push.Files to Modify/Create
tools/modules/services/service_voice.nu— new, ~203 lines, cloned structure fromservice_indexer.nu.tools/modules/services/mod.nu— append one line. Rebase before push.Implementation Plan
Clone the template file. Copy
service_indexer.nutoservice_voice.nu. Imports at lines 3–4 verbatim. Module docstring at line 1 →# service_voice.nu — manage the hero_voice service lifecycle through hero_proc..Rename consts (
service_indexer.nu:6-9):SVX_SERVICE_NAME = "hero_voice"SVX_FORGE_LOC = "lhumina_code/hero_voice"SVX_BINARIES = ["hero_voice" "hero_voice_server" "hero_voice_ui"]SVX_ACTIONS = ["hero_voice_server" "hero_voice_ui"](Keep the
SVX_prefix — it matches the pattern the template uses.)Server action (
service_indexer.nu:11-50) insvx_server_action:svc_bin "hero_voice_server" $root/name: "hero_voice_server".env: {RUST_LOG: "info"}unchanged./hero_voice/rpc.sock(confirmed byhero_voice_server/src/main.rs:44-54).script: $binkept — noservesubcommand;main.rs:56-59callsrun_server()directly.UI action (
service_indexer.nu:52-91) insvx_ui_action:svc_bin "hero_voice_ui" $root/name: "hero_voice_ui".env: {RUST_LOG: "info", HERO_VOICE_STT_LOCAL: "true"}. Confirmed:HERO_VOICE_STT_LOCALis read bycrates/hero_voice/src/local_transcriber.rs:31; instantiated only fromhero_voice_uiinws.rsandtranscribe_handler.rs. The server binary does not construct aLocalTranscriber./hero_voice/ui.sock(confirmed byhero_voice_ui/src/main.rs:71-73).Service config (
service_indexer.nu:93-105):description: "Hero Voice — speech-to-text and text-to-speech server and admin UI".svx_drop_registration(service_indexer.nu:107-113): identical after rename.install(service_indexer.nu:115-127): identical after rename. Note:svc_bins_ok $SVX_BINARIES $rootshort-circuits when all three binaries are present (lib.nu:65).svc_cargo_installis sufficient —hero_voice/Cargo.toml:1-10is a pure virtual workspace, plaincargo build --releasebuilds all three bins.start(service_indexer.nu:129-180): identical after rename. Audit the literal error string at line 156 — change"hero_indexer_server not found"→"hero_voice_server not found". Print messages at lines 162, 165, 179 update via the$SVX_SERVICE_NAMEinterpolation naturally; the two action-specific prints (registering hero_voice_server action/..._ui action) andproc logs get hero_voice_serverneed explicit renames.stopandstatus(service_indexer.nu:182-202): identical after rename.statuscallssvc_require_proc "service_voice" $root.Wire into
mod.nu. Appendexport use service_voice.nuat the end. Rebase againstorigin/developmentimmediately before push — other agents may have added entries in parallel.Acceptance Criteria
tools/modules/services/service_voice.nuexists (~200 lines), structurally identical toservice_indexer.nuexcept for the name/binary/socket/description substitutions and the UI env addition.service_voice installbuilds all three binaries and places them in~/hero/bin/(or/root/hero/bin/with--root).service_voice installsecond time short-circuits with→ hero_voice binaries already in place — skipping build.service_voice startregisters both actions + the service, starts it.HERO_VOICE_STT_LOCAL=true, verified viaproc action describe hero_voice_ui.service_voice statusreports running with both actions healthy.service_voice stopcleanly unregisters.--rootoptional; user-level default.mod.nuexportsservice_voiceand existing services still load.Notes
Cargo.tomlhas[workspace]only, no root[package]).svc_cargo_installis sufficient — no--workspaceflag like books needed.hero_voice_server/main.rs:56-59andhero_voice_ui/main.rs:75-78both callrun_server()directly from#[tokio::main] async fn main(). Usescript: $bin, notscript: $"($bin) serve".$HERO_SOCKET_DIR/hero_voice/rpc.sock(main.rs:42-54), UI$HERO_SOCKET_DIR/hero_voice/ui.sock(main.rs:59-73). Both honourHERO_SOCKET_DIR, matchingsvc_sock_base.crates/hero_voice/src/local_transcriber.rs:31; instantiated only fromhero_voice_ui. Server binary never constructs aLocalTranscriber.hero_voiceCLI binary: listed inbuildenv.shBINARIES, sosvc_cargo_installcopies it, but it is not registered as a hero_proc action (matcheshero_voice.toml, which only declares[server]and[ui]).use tools/modules/services *service_proc start --rootservice_voice install --root— 3 binaries in/root/hero/bin/service_voice install --rootagain — short-circuit messageservice_voice start --reset --root— both actions registered + startedproc action describe hero_voice_ui --root— verifyHERO_VOICE_STT_LOCAL=trueproc service status hero_voice --root— running, 0 restartscurl --unix-socket /root/hero/var/sockets/hero_voice/rpc.sock http://localhost/— HTTP responsecurl --unix-socket /root/hero/var/sockets/hero_voice/ui.sock http://localhost/— HTTP responseservice_voice start --root— idempotent "already running"service_voice stop --root— clean unregisterservice_voice status --root—service 'hero_voice' not foundCritical Files for Implementation
Spec addendum — alignment with commits landed 2026-04-20
Audited commits from kristof5, xmonader, and despiegk that landed after the last merge (PR #91). Summary:
85dfdc7(despiegk) — service split: movedservices.nu→ per-file modules and added--reset+svc_bins_okshort-circuit to every service'sinstall. The spec's template (service_indexer.nu) was introduced in this commit and already carries the new pattern, so no change to the module code itself is needed — the existing plan is correct.c6ad517(kristof5) —svc_require_sudonow usesis-admininstead of barewhoami == "root"; error message rephrased. Transparent to consumers; the module just callssvc_require_sudoand inherits the new behaviour.c6ad517+packages.nu(new) — addedservice_install_allthat iterates all services with--update. Every new service module should be registered there.5796526(kristof5) — delegates template service builds to the service modules'install. No per-module change required beyond the standard--resetflag we already plan.3d809fb(despiegk) — forge partial-match: transparent viasvc_update.9229d7e(xmonader) — build-verification guardrail: docs only; smoke-test already covers parse-check.Added to the implementation plan
Step 10 (Wire into
mod.nu) — keep as specified.New Step 11 (Wire into
packages.nu):tools/modules/services/packages.nu:use service_voice.nuto the imports block (currently lines 17/26 haveservice_books.nu/service_indexer.nu— append at the end of the use block).{name: "service_voice", run: {|| service_voice install --update}}to theserviceslist insideservice_install_all(append at the end, after line 62service_indexer; order inside the list is only meaningful for services with hard install-time dependencies, which voice does not have).Renumber: the old Step 11 (syntax check) becomes Step 12; the old Step 12 (smoke test) becomes Step 13.
Acceptance criteria — add one entry:
service_install_alliteratesservice_voiceas one of its entries — no separate test required; just grep the file.Other in-flight / merged work — alignment check
85dfdc7to pick up the--reset+svc_bins_okpattern. No manual retrofit needed.HERO0_BASE_URLbrittleness note inservice_biz.nuis still accurate; nothing in the recent landings changes it.servesubcommand carve-out forservice_books.nuis still accurate (confirmed byscript: $"($bin) serve"at line 79 and 124 of the current merged file).No other changes to the voice spec. The 10 implementation steps remain otherwise as posted.
Spec addendum correction — packages.nu structure changed
Pulled two more commits that landed after my previous addendum:
5e032e5(kristof5, 10:29) — adds--templateflag toservice_install_all(mirrors built binaries into/home/template/hero/bin).abd0a1d(kristof5, 10:44) — splits the singleserviceslist intoservices_core(proc, router, mycelium, code, codescalers, lib_rhai, embedder) andservices_extra(everything else), adds--coreflag.Correction to Step 11 in the addendum: the
serviceslist no longer exists as a single entity. Voice goes intoservices_extra, not a flat list.Updated Step 11 (Wire into
packages.nu):use service_voice.nuto the import block at the top (afteruse service_lib_rhai.nuor any appropriate position — order in theuseblock is not significant).{name: "service_voice", run: {|| service_voice install --update}}to theservices_extralist insideservice_install_all(append at the end; order insideservices_extrais not dependency-sensitive for voice).services_core— the core list is reserved for bootstrap services (proc/router/mycelium/code/codescalers/lib_rhai/embedder) that must come up first for a minimal install.Nothing else in the spec changes. The voice module code itself is unaffected by these packages.nu refactors (the module's
installcontract is whatservice_install_allcalls either way).Other files touched in the six new commits (
abd0a1d,3e24368,7864695,f4ec800,ec946fe,5e032e5) are all installer / multiuser / hero_loader refactors — no impact on voice.Implementation summary
Changes
tools/modules/services/service_voice.nu— ~203 lines, near-verbatim copy ofservice_indexer.nuwith one UI-env addition (HERO_VOICE_STT_LOCAL: "true").tools/modules/services/mod.nu— appendedexport use service_voice.nu.tools/modules/services/packages.nu— addeduse service_voice.nuand{name: "service_voice", run: {|| service_voice install --update}}entry inservices_extra.Test results on Hetzner
Module-level assertions: all PASS.
service_voice.nustandalonepackages.nuwithuse service_voice.nuuse mod.nu *exports the 4 subcommandsservice_proc start --rootEnd-to-end smoke test blocked by upstream build issue.
service_voice install --rootfails during the cargo build ofwhisper-rs-sys(hero_voice's STT dependency):The failure is in the nested CMake build that
whisper-rs-sys'sbuild.rskicks off. Root of the failure is the interaction between sccache (configured asRUSTC_WRAPPERwith server running under root's hero_proc) andwhisper-rs-sys's CMake-driven C++ compilation. Directgcc -MD -MT foo -MF foo.o.d -c foo.cworks fine; wrapping through sccache + make + CMake breaks.Reproduced class of issue:
error writing dependencies to /home/<user>/hero/build/cargo/release/deps/herolib_tools-*.d: Permission deniedduringservice_core install. Same "permission denied on a file the user owns" symptom.Not in PR scope. The voice module is correct:
service_indexer.nu's canonical shape (the latest template landed in 2026-04-20 by despiegk in85dfdc7).HERO_VOICE_STT_LOCALalongsideRUST_LOG— this is the one delta from the baseline.RUST_LOG(matcheshero_voice.toml's[server.env]).mod.nuandpackages.nuwire the module in with the correct ordering (voice →services_extra, notservices_core, since it's not a bootstrap prerequisite).Once the whisper-rs-sys / sccache interaction is resolved upstream or sccache is disabled for this build, the install + full end-to-end cycle will work without any change to this module.
Acceptance criteria
install,start,stop,status.mod.nure-exportsservice_voice.packages.nuservices_extracontainsservice_voice.HERO_VOICE_STT_LOCAL=trueon top ofRUST_LOG=info.RUST_LOG=info.hero_proc-downerror paths produce the clean remediation message (PASS on 1a/1b/1c).service_voice installsucceeds end-to-end — blocked by upstream whisper-rs-sys/CMake+sccache interaction on the current Hetzner build environment. Not addressable in this module's scope. Parallels the upstream gating we saw onservice_biz.nu(placeholderrun_server) andservice_os.nu(WASM assets prereq).Follow-up (out of scope)
The
Permission deniedon.d/.o.dfile writes inside cargo build directories is hitting multiple services and multiple users. Worth a dedicated ticket to either:RUSTC_WRAPPER=sccacheopt-in per user instead of default, orTracked informally alongside issue #79 (the
service_proc.nu:270bug) — worth splitting into a proper follow-up once a maintainer triages.PR opened: #93