Skip to content

feat(voice): add Phase 1 voice interaction for Lucia - #421

Closed
Ticed wants to merge 25 commits into
infernode-os:masterfrom
Ticed:dev
Closed

feat(voice): add Phase 1 voice interaction for Lucia#421
Ticed wants to merge 25 commits into
infernode-os:masterfrom
Ticed:dev

Conversation

@Ticed

@Ticed Ticed commented Jul 14, 2026

Copy link
Copy Markdown

Summary

Adds the Phase 1 release candidate for single-host macOS voice interaction in Lucia.

This introduces provider-backed streaming speech, hands-free voice-mode controls, local speech-helper installation, and supporting tests and documentation.

This is opened as a draft for maintainer review. Automated speech regression is now included in CI; only the normal PR checks and a final interactive macOS smoke pass remain
before merge.

Changes

  • Add CoreAudio-backed macOS capture and playback support.
  • Add a namespace-composable speech-provider contract through speech9p and speechshim9p.
  • Add Kokoro TTS, Parakeet streaming STT, Whisper fallback, and wake/VAD helper integration.
  • Add half-duplex echo protection, microphone lifecycle control, cancellation, chimes, and streaming partial/final records.
  • Add the resident voicemode state machine and unified voice-mode entry through:
    • the Voice resource
    • the compose voice control
    • Ctrl+Space
    • Esc-V
    • Option/Alt+V
    • /voice mode on|off
  • Preserve keyboard compose text and make Esc return cleanly to keyboard mode.
  • Add utterance grace, append, deduplication, spoken cancellation, approvals, denial, refinement, confidence confirmation, and a capped queued follow-up.
  • Add an LLM-free speech test mode and configurable local OpenAI-compatible LLM support.
  • Add helper installation, regression tooling, architecture documentation, Phase 1 exit criteria, and Phase 2 scope.

Testing

Completed during implementation:

  • tools/speech-regress.sh — 14 suites passed on macOS
  • Speech regression suite added to Linux CI
  • tools/verify-dis-paths.sh
  • Bash and Python syntax checks
  • macOS SDL build
  • New and expanded speech, provider, voice-mode, Lucia, installer, and host-side tests

Before final merge:

  • Confirm the GitHub Actions and full repository test results.
  • Complete one interactive macOS smoke pass covering microphone transcription, audible playback and cancellation, voice controls, and an LLM-backed turn.

Phase boundary

Phase 1 covers usable single-host macOS voice interaction.

Two-host/Jetson deployment, remote-audio productization, model distribution, richer queue management, and other cross-host work remain Phase 2 or later.

Checklist

  • Code follows the existing style of the files modified
  • No .dis build artifacts were committed inside appl/ or tests/
  • Documentation was updated
  • No secrets, API keys, or credentials are included

Ticed added 25 commits July 7, 2026 07:02
Move the local Lucia speech bridge work onto infernode-os/infernode so future PRs target the maintained canonical repository instead of the archived NERVsystems fork.

Constraint: NERVsystems/infernode is archived/read-only; Ticed/infernode-os remains the PR fork for infernode-os/infernode.

Rejected: Continue developing against NERVsystems/infernode | archived source can no longer be an operational target.

Confidence: high

Scope-risk: moderate

Directive: Treat the archived NERVsystems branch as historical input only; keep future work on infernode-os/infernode and push PR branches to origin.

Tested: git diff --check; targeted Limbo compiles for lucibridge/luciuisrv/voicemode/speech9p/luciuisrv_test/speech9p_voice_test; ./build-macos-headless.sh; tests/host/audio_macos_test.sh ctl; luciuisrv_test 36 passed; speech9p_voice_test 6 passed.

Not-tested: full ./run-tests.sh; full appl/cmd mk install is blocked by unrelated appl/cmd/git/push.b interface mismatch.
Move Phase 1 voice mode onto a single speech-provider contract so wake, listen, say, cancel, and voices can be served by speechshim9p, a Parakeet export, or a remote 9P provider without changing the Lucia-facing /n/speech surface.

speech9p delegates streaming speech I/O to the provider mount and parks blocking wake/listen/hear/say reads asynchronously so ctl and cancel remain live for barge-in. voicemode becomes a resident input-mode-driven state machine, lucibridge reads keyboard and voice inputs concurrently, Lucifer maps Esc to keyboard mode, and boot starts the speech stack before lucibridge.

Constraint: helper binaries remain external and optional; the in-tree shim adapts them through namespace files and soft-fails when unavailable.

Rejected: Run wake/listen/TTS helpers directly inside speech9p | blocking helper reads freeze the 9P serveloop and break cancel/barge-in.

Confidence: high

Scope-risk: moderate

Directive: Keep future speech engines behind the provider 9P contract; do not add engine-specific paths to lucibridge or voicemode.

Tested: speechshim_test 5/5; speech_wake/speech_listen/speech_kokoro 12/12; voicemode 6/6; speech9p_voice 6/6; luciuisrv 36/36; lucibridge 49/49; audio_macos smoke; dis-path verifier; targeted regressions for touched modules.

Not-tested: full exhaustive suite was stopped at user request after targeted coverage; live dogfood with real Kokoro/whisper/openWakeWord helpers, mic permission, wake word, and latency targets; remote-audio Phase 2 path.
Route speechshim9p playback and capture through configured namespace paths so local helpers, remote processing hosts, and remote capture devices can use the same provider contract without adding engine-specific remoting paths.

speechshim9p now accepts audiodev, capturedev, micmode, and capturerate ctl settings; micmode device pumps s16le PCM from the capture device into listen/wake helper stdin, while speech9p forwards the routing knobs to the mounted provider. The remote-audio documentation now describes the local, remote-engine, and remote-microphone topologies as the same 9P composition.

Constraint: remote voice I/O must remain ordinary namespace composition through the speech provider mount, with helper binaries still external.

Rejected: Require bind-based /dev/audio replacement or special speech9p remoting logic | audio routing can be expressed as provider ctl state and namespace paths.

Confidence: high

Scope-risk: moderate

Directive: Keep future remote speech topologies behind provider ctl keys and 9P mounts; do not add per-device or per-host branches to lucibridge, voicemode, or speech9p.

Tested: speechshim_test 7/7 including AudioRouting and DeviceCapture; speech_wake 4/4; speech_listen 4/4; speech_kokoro 4/4; speech9p_voice 6/6; verify-dis-paths; git diff --cached --check.

Not-tested: live remote processing host, Android/phone microphone export, real helper stdin-stream configuration, and full seamless voice UX dogfood.
tools/install-speech-helpers.sh sets up the Phase 1 helper stack on the
host: Homebrew whisper.cpp, a pinned venv with kokoro-onnx and
openwakeword, model downloads, and provider-contract wrappers
(kokoro-cli, whisper-stream-cli, openwakeword-cli). It prints the
/n/speech/ctl block to paste after mount, including the micmode device
stdin-PCM variant. Host smoke coverage in
tests/host/speech_helpers_test.sh exercises the no-mic paths.

The speech contract gains a write-only chime file (wake/done/on/off
earcons) and a duplex ctl option. In half duplex — now the boot default
— speechshim9p keeps capture drained but suppresses wake/STT delivery
while playback or chimes are active, so TTS output can't re-trigger
voice mode. Esc barge-in still works; headset setups can restore spoken
barge-in with duplex full.

voicemode grows a listen timeout (-t) and wake cooldown (-w), parses
partial/final/error listen records, filters whisper silence
hallucinations ("thank you", ...), surfaces live partial transcripts
and errors through the activity context, and defers wake handling while
an approval is pending. lucictx renders a Resources section with
status indicators, and clicking the /n/speech row (or pressing v in
lucifer) toggles voice input mode.
…sh exercises the whole microphone -> STT -> TTS loop

with no LLM, no login, and no API key, for validating helper installs
and audio topologies without per-turn cost.

Headless (default): new appl/cmd/speechtest.b bootstraps speechshim9p + speech9p in its own namespace, prints live partials to the terminal, and answers every non-junk final by speaking a hard-coded phrase (-p) or the transcript itself (-e). -c/-M compose the remote topologies from docs/SPEECH-REMOTE-AUDIO.md.

GUI (--gui): boots the full lucifer desktop via the new
lib/lucifer/boot-speechtest.sh with voicemode in a new test mode (-p/-e): finals bypass conversation/voiceinput, are shown as a "Heard" dialogue line, and are answered with the canned phrase — wake, live partials, chimes, barge-in and control intents unchanged. boot.sh also gains a $speechhelperbin hook that applies the helper-installer ctl block automatically instead of requiring a manual paste.

Tests: tests/speechtest_test.b (5) and three TestMode* cases in
tests/voicemode_test.b (17/17).
The shim's streaming helpers (whisper-stream, openwakeword) start
lazily on the first listen/wake read — nothing touches the mic at
boot — but were never stopped, so after the first voice session the
microphone stayed open forever.

New `mic on|off` ctl key in speechshim9p: `off` kills the mic-side
helpers and the capture pump's device fd, and pending listen/wake
reads return "error: mic off" instead of restarting them; the next
read re-arms the mic. speech9p forwards the key with the other audio
routing knobs, and voicemode writes `mic off` on voice-mode exit.

The Voice chip / Esc-V toggle is thus the mic's on/off switch: open
only during a voice session, with wake-word turn gating unchanged
within it.

Tests: speechshim_test MicOffReleasesHelpers (11/11), voicemode_test
exit-path mic-off assertion (17/17).
wm/logon skipped the password screen whenever factotum held any key, but lib/sh/profile provisions API-key fallbacks into factotum before logon runs. A configured API key could therefore look like an authenticated desktop session.

Set secstoreautounlock only on the genuine SECSTORE_PASSWORD auto-unlock path, and require that marker together with factotum keys before skipping logon.

Tested: tests/host/secstore_logon_test.sh (12 checks); appl/wm/logon.b rebuilt with mk.
Remove the buffering filter from the installed whisper wrapper so final records reach InferNode immediately. Preserve carriage-return cleanup in the shell loop, keep helper stderr, and add capture/length controls plus a host relay regression that catches buffered output.

Frame helper stdout as newline-delimited records in speechshim9p, retain bounded stderr diagnostics, and add listen off so inactive turns cannot queue ambient or self-speech. Forward the new controls through speech9p, clean up suppressed wake helpers, and preserve multiword wake phrases.

Make failures visible through voicemode logging and Voice-chip status, acknowledge chip toggles immediately, report LLM configuration at startup, and auto-detect an installed helper stack during desktop boot.

Add tools/speech-regress.sh as the one-command voice regression entry point and include speechtest_test in the test mkfile.

Tested: tools/speech-regress.sh (9 suites); microphone-dependent wake/STT smoke skipped outside an interactive TCC-approved session; git diff --cached --check.
Make Lucia voice turns completion-aware and interruptible, with queued streaming TTS, live drafts, voice controls, cancellable approvals, confidence confirmation, and SDL Option-V entry.

Add stdin PCM Whisper streaming, namespace-composable remote audio launch scripts, and a loadable SpeechEngine module ABI with provider-backed implementation. Expand the speech regression suite and update the architecture and acceptance documentation.

Tested: tools/speech-regress.sh (12 suites); tools/verify-dis-paths.sh; bash and Python syntax checks; macOS SDL build. Microphone-dependent smoke coverage remains an interactive TCC acceptance gate.
Build the tracked stdin-PCM adapter against parakeet.cpp, select the generated helper configuration at boot, and keep Whisper as the soft fallback when the EOU model is unavailable.
Show a short send countdown, append additional finals into the pending utterance, deduplicate repeated finals, and let spoken cancel discard the turn before submission.
Route the compose button and Ctrl+Space through the same input-mode switch as the Voice chip and keyboard shortcuts, removing the duplicate one-shot dictation path.
Default the shim to 22050 Hz so Inferno does not silently fall back to 8000 Hz and stretch Kokoro playback to roughly three times its intended duration.
Retry the canonical model URL and its explicit download form, reject short responses before atomic installation, preserve complete existing models, and cover the behavior with a host-side fake-curl test.
Allow one visible refinement while an activity is busy, reject additional spoken finals until the activity returns idle, and cover the queue cap and reset behavior in the daemon tests.
Make single-host human acceptance the Phase 1 gate and recast Phase 2 around validating and productizing the already-implemented remote provider, audio-routing, deployment, model-distribution, and queue foundations.
Compose Lucia, voicemode, the speech provider stack, llmsrv, and lucibridge against deterministic loopback fixtures. Run the scenario in the blocking speech CI suite and leave only physical audio and GUI input checks for human acceptance.
Merge the current canonical development base into the Phase 1 voice-mode candidate while preserving the resident voice-mode control path and incorporating upstream speech control-name hardening. Regenerate the conflicted Lucia, speech9p, and logon bytecode from the resolved sources.\n\nTested: tools/speech-regress.sh (15 suites); tools/verify-dis-paths.sh; speech9p_security.sh; targeted macOS SDL3 and headless emulator builds; full test bytecode build.\n\nNot-tested: real microphone, audible playback, GUI controls, cross-host audio, and live external Git integration. The repository-wide macOS runner remains blocked by canonical baseline lifecycle and namespace-isolation failures outside the speech subset.
Document that the current anonymous broad-device exports do not preserve InferNode capability attenuation. Make the post-Phase 2 security audit an explicit release gate rather than treating network reachability controls as an internal security boundary.\n\nTested: git diff --check.\n\nNot-tested: remote two-host transport or adversarial-client behaviour; both remain later human and audit gates.
Replay the installer-selected speech control file in both dogfood launchers so Parakeet streaming and Kokoro remain the configured providers.

Render live hypotheses and the grace countdown as a bordered unsent conversation turn, keep the Voice resource compact, and preserve a visibly locked keyboard compose buffer until voice mode exits.

Add the draft-status UI surface and regression coverage for launcher configuration, daemon state, and pending-turn presentation.

Tested: tools/speech-regress.sh (15 suites)

Tested: tools/verify-dis-paths.sh

Tested: bash -n tools/speech-test.sh
Rebuilt the four conflicted .dis from the merged sources. INF-33
@pdfinn

pdfinn commented Aug 20, 2026

Copy link
Copy Markdown
Member

Thanks for this — it's a serious piece of work. The branch is current with master, CI is green, the tests are genuinely behavioral rather than smoke, the docs are unusually candid about security posture, and the core design (the /n/speech provider seam, the half-duplex echo scheme, the voicemode state machine, compose-text preservation) is sound. The review below found four correctness blockers, one supply-chain blocker, several posture decisions that need to be made explicitly on the maintainer side, and a set of smaller fixes. None of it is fatal to the approach; with the blockers fixed this is a solid Phase 1.

Blockers

1. Typed input silently vanishes in voice mode (merged-tree bug). The voice-mode mutual-exclusion path in appl/cmd/lucibridge.b does writemsg("assistant", "voice mode active — …"). Master's hardened luciuisrv (d1d16f9) validates roles against human|veltro|system|tool and rejects assistant with Rmsg.Error, which lucibridge only logs to stderr. Net merged behavior: with voice mode on, a typed non-slash message is consumed from the input queue, discarded, and the explanatory bubble never renders — the user's text disappears with no feedback. The merge is textually clean; the bug only exists in the combined tree. Use veltro or system, and ideally preserve the typed text rather than dropping it.

Related: the PR's lucibridge.b pre-image is not master — it's master plus the unmerged #505 (fix(security): report failed tool mutations). Merging as-is silently carries that change. Please rebase that file onto master proper (every other modified file is already clean against master tip).

2. boot.sh can abort boot for everyone. The speech probe builds /n/local^$speechprefix^/speech.ctl.sh with no $#speechprefix guard. Where os sh yields nothing (Windows Nt emu, devcmd not bound, restricted namespaces), speechprefix is the empty list and the ^ concatenation raises sh: null list in concatenation, aborting boot.sh before lucibridge, voicemode, the Tasks board, and the plumber start. This hits users who never enable voice. Both ftest probes need an if {! ~ $#speechprefix 0} guard.

3. Supply chain: the installer must pin what it executes. tools/install-speech-helpers.sh clones mudler/parakeet.cpp at default-branch HEAD (--depth 1 --recurse-submodules), compiles it, and executes the resulting binary — whoever controls that repo's HEAD gets code execution on every installer run. The four model downloads are similarly unpinned: Kokoro model/voices from GitHub release assets (replaceable in place), Whisper and Parakeet GGUF via Hugging Face resolve/main (mutable branch refs), two of them from individuals' conversion accounts rather than the model authors. There is no checksum or signature verification anywhere in the file — the only integrity gate is a size floor on the whisper model. Before merge: pin the parakeet.cpp clone to a commit SHA, pin the HF URLs to revision commits, and add a SHA256 manifest for the model files. (The rest of the installer is well-behaved: no sudo, no pipe-to-shell, atomic downloads, everything under ~/.local/share/infernode-speech.)

4. The CoreAudio driver is architecturally wrong for the emu kproc model — and it's dead code. emu/MacOSX/audio.c blocks in pthread_cond_wait while holding the direction QLock (audio_file_read holds inlock across ringread). Inferno's kill path (oshostintrpthread_kill(SIGUSR1)) interrupts syscalls but does nothing to a condvar wait, so on the TCC mic-denied posture — where the input AudioQueue starts but never delivers buffers, exactly the case tests/host/audio_macos_test.sh describes — the reading proc blocks forever, is unkillable from inside Inferno, and audio_file_close deadlocks behind the held lock. Other backends wrap host blocking in osenter()/osleave() (see emu/FreeBSD/audio.c) for precisely this reason.

Meanwhile emu/MacOSX/emu still lists audio audio-sdl3, so the new driver never builds — yet the CoreAudio/AudioToolbox link flags and the driver-specific test assertions (cannot start CoreAudio …: -66680) ship anyway, and the host test silently exercises the wrong driver. If the CONF is ever flipped, the new driver also regresses drain-on-close (INFR-185: AudioQueueStop(outq, true) + ringfree drops up to 256KB of buffered playback) and the play_buffer_ms/rec_buffer_ms ctl verbs voice setup writes (INFR-194 — audioparse returns 0 on unknown verbs, so the write fails with Ebadarg). Either flip the CONF in this PR and reconcile explicitly with audio-sdl3.c, or drop the driver, the mkfile link flags, and the CoreAudio test assertions until the phase that wires it.

5. Flush/Clunk permanently wedges the wake/listen streams. In both speech9p.b and speechshim9p.b, Flush cancels the async tag and Clunk drops the fid state, but neither clears listenbusy/wakebusy, cancels the spawned asyncwake/asynclisten proc, nor resets the shared provider fd. The parked proc stays blocked in readmountedfd (a wake read blocks indefinitely by design), and since the busy flag only clears in asyncdone, every subsequent open+read returns "error: wake busy" forever. It self-heals only along voicemode's exact exit choreography (cancel + mic off killing the helper); any other client — speechtest, a manual cat, a crash — leaves the stream dead. Flush/Clunk of a parked streaming read needs to reset the provider fd and clear the busy flag (or key the busy state on fid and clear it at Clunk). The bug exists identically in both servers because the async scaffolding is hand-copied between them — see the de-duplication note below.

Posture decisions to make explicit (maintainer side)

These are flagged for discussion, not unilateral fixes — but they need conscious sign-off rather than riding in implicitly:

  • lib/voice/speech-{terminal,engine,capture} ship in release artifacts (lib/ is in the release copy loop) and speech-terminal runs /lib/voice/listen, i.e. listen -A 'tcp!*!…' { export /dev } — anonymous export of the entire /dev tree on all interfaces. They're dormant, operator-invoked, and docs/SPEECH-REMOTE-AUDIO.md honestly labels them development-only, but this is the same shape of posture that got tests/agent-harness/ ring-fenced. We need to decide: accept as documented dev tooling, or keep the trio out of the release copy loop until the Phase 2 auth work.
  • Boot executes a host-writable file verbatim. boot.sh now runs sh on ~/.local/share/infernode-speech/speech.ctl.sh inside the boot namespace at every boot — a quiet persistence/injection hook for anything with host-user file write. A restricted key value parser instead of sh would close it; at minimum the trust decision deserves a comment.
  • The mic is armable by any /mnt/ui-rw holder. input-mode and conversation/voiceinput are 8r666, so anything holding the UI service rw can flip the desktop into voice mode (muting typed input — see blocker 1) or inject "spoken" turns through the path that deliberately bypasses the keyboard pause. Incremental over the pre-existing /mnt/ui exposure, but mic activation without user gesture is a new capability.
  • The logon tightening should probably be its own PR. Requiring secstoreautounlock=1 in addition to factotum keys before skipping login ("Never let an API key bypass the login screen") is a correct fix, but it changes boot behavior for every user regardless of voice, and bundling it here makes it invisible in release notes.
  • Every GUI boot now starts the full speech surface unconditionally — two resident 9P servers plus the voicemode daemon, with "no speech helpers found" warnings on machines that never opted in. The degradation design is otherwise right (mic only opens on a voice session, helpers soft-fail); the question is whether default-on is intended for Phase 1.

Worth fixing, not blocking

  • /n/speech/cancel is a no-op for every engine except the provider path. dosay for cmd/api/local/module routes through playpcm, which does one blocking write of the whole buffer and never checks cancelreq — barge-in only works with the kokoro/shim backend. Fine for Phase 1's shipped config, but the cancel file should reject or document non-provider engines rather than accept a write that does nothing. Also: cancelreq is only reset on a sayq write, so after one cancel the cancel file reads "cancel pending" indefinitely.
  • The Alt+V chord lives in the portable emu/port/draw-sdl3.c and SDL_KMOD_ALT includes RALT — on European layouts AltGr+V becomes untypeable on Linux/Android, and the app receives raw ESC,v instead. Gate it to macOS desktop (__APPLE__ && !TARGET_OS_IOS). Also unguarded: key autorepeat (held Alt+V toggles repeatedly) and a stale chord flag if focus is lost while Alt is held.
  • Esc chording collisions in lucifer.b kbdproc. The ESC state has no timeout and the chord is intercepted before routing to workspace apps, so Emacs-style M-v in any hosted editor flips the desktop into voice mode; in voice mode, bare Esc is consumed globally, and terminal arrow keys (ESC [ A) half-escape — the ESC exits voice mode and literal [A lands in the compose box.
  • lib/voice/speech-engine was never run end-to-end: it invokes bare speechshim9p, which can't resolve on Inferno sh's (/dis .) path — the binary lives at /dis/veltro/speechshim9p.dis (boot.sh gets this right). The script always hits its own "did not mount" guard. The accompanying voice_scripts_test.b can't catch it because it only greps the script text.
  • CI cost. The speech-regress.sh step re-runs Limbo suites that the existing runner.dis full-suite step auto-discovers minutes later in the same job — every PR pays twice, inside a 30-minute timeout that the worst-case suite budget can approach. Suggest dropping the Limbo suites from the CI step (keep the three host tests) or path-filtering it. Separately, speech-regress.sh and speech_helpers_test.sh call timeout(1), which stock macOS doesn't ship — broken for the local workflow on the platform the feature targets.
  • Dead interface surface. Speech->Partial is added to module/speech.m with a doc comment but nothing constructs or consumes it. The SpeechEngine module contract is batch-shaped (synthesize returning PCM, recognize(audio)) and its only implementation, speechprovider.b, has to no-op both semantics to fit its streaming-file reality — and nothing in-tree wires it. Suggest deferring both until a genuine in-process engine exists.
  • De-duplicate the styx scaffolding. The async-helper machinery, serveloop, Flush/Clunk handling, and the INFR-28 raw-Rmsg.Read streaming reply are copy-pasted between speech9p.b and speechshim9p.b ("same pattern as speech9p"). Blocker 5 exists twice because of this; the copies will drift.
  • Mount convention. New defaults /n/speechshim and /n/parakeet (and voicemode's /n/speech) — house rule is /mnt/* for app-authored mounts; the pre-existing /n/speech is the precedent you followed, so this is understandable, but let's settle it now before Phase 2 multiplies the mounts.
  • tools/parakeet_stream.cpp: mel_buf is only reset when the model emits end-of-utterance, and append_mel_frames recopies the whole buffer per block — under continuous non-speech audio memory grows without bound and per-block cost grows linearly. Trim already-fed frames (keep pre_encode_cache_size of history). Also guard sess.text().substr(finalized_chars) with min(finalized_chars, text.size()) — a hypothesis retraction currently terminates the helper.
  • Small cleanups: readuserinput() in lucibridge is dead after the pretoolapproval rewire yet was modified — delete it; the voice-toggle logic is triplicated across luciconv, lucictx, and lucibridge; luciconv.voiceactive() does a synchronous 9P open/read per keystroke instead of caching the input-mode event it already receives; Qconvcontrol/draft fields in luciuisrv skip the safeeventtext/safeattrtext sanitization pattern d1d16f9 just established (contained, but the next audit pass will flag it).

What's good

Worth saying explicitly: the async-read design (parking blocking helper reads, completing via the helper channel so ctl/cancel stay live during a blocking wake read) is the right architecture and the INFR-28 offset-bypass streaming reply is handled correctly; the half-duplex echo suppression and the grace/append/dedup/confirm state machine are internally consistent; compose-text preservation is done right (server-side draft file, compose buffer untouched); the new luciuisrv files follow house patterns; the voicemode/shim/speech9p test suites run against fake helpers so they don't silently skip in CI; the installer's hygiene (atomic writes, no sudo, env-overridable URLs) is good modulo pinning; and the docs' honesty about the remote-audio security posture is appreciated.

Suggested path to merge

  1. Fix blockers 1, 2, and 5; rebase lucibridge.b onto master proper.
  2. Pin the installer (commit SHAs, HF revisions, SHA256 manifest).
  3. Drop the CoreAudio driver + link flags + CoreAudio test assertions from Phase 1 (or flip the CONF and reconcile with audio-sdl3.c — but that's a bigger conversation).
  4. Split the logon change into its own PR.
  5. We'll settle the posture questions (release inclusion of lib/voice/speech-*, the boot ctl-script execution, the 8r666 mic surface, default-on boot) in this thread before undraft.

🤖 Generated with Claude Code

pdfinn added a commit that referenced this pull request Aug 21, 2026
…ctual review)

A counterfactual audit tested the corpus against PR #421 (the
non-compliant voice PR that motivated the guidance): had the docs
existed, which defects would they have prevented? The architectural
class scored well; the consistent blind spot was every boundary with
the host. This closes the eight gaps it filed:

- emu-dev skill (new): kproc kill path (oshostintr interrupts syscalls,
  not condvar waits; osenter/osleave; never hold a QLock across a
  blocking wait), CONF/mkdevlist wiring (unwired driver = dead code),
  the incumbent rule, drivers-deliver-events layering.
- DESIGN-PRINCIPLES: 'The host boundary' section (pin + checksum
  everything fetched; installers never build-and-execute unpinned HEAD;
  boot never executes host-authored files; placement is shipping via
  the release copy loop); 'Writes are RPCs — on both sides'; smell rows
  for global busy flags and UI policy in drivers.
- LIMBO-FOR-GO-PROGRAMMERS: null-list concatenation and
  failed-redirection rows hoisted into the shell table as the single
  lookup point.
- Tutorial: caller-side dual of failed-at-the-writer; fid-is-the-session
  pointer on the blocking-reads bullet.
- limbo-test: norms — behavior not source text, a suite that skips in
  CI guards nothing, the contract test is the durable 'I ran it'.
- limbo-dev: pre-image hygiene before opening a PR.
- Maintainer ruling applied: dis/tests/*.dis is TRACKED — stale
  gitignore line dropped, CLAUDE.md/limbo-dev/limbo-test now agree.
- Maintainer ruling applied: sketch-first is policy — CONTRIBUTING
  states interface PRs without a proposal issue are returned; the
  style-gate workflow adds a soft proposal-link annotation; PR template
  gains the pinned-and-verified row; style gate documents its
  style-not-semantics scope.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Eo29oZp7mPig1XekwRPsq
pdfinn added a commit that referenced this pull request Aug 21, 2026
…ulings (#516)

* docs: absorb the first field-review's lessons into the guidance

The PR-421 voice review — the first real consumer of the design
principles — surfaced rules the docs should have stated: the
null-list-in-concatenation boot-killer joins the Limbo shell gotchas
(the review cited it as already present; now it is), the ninep-server
skill gains 'the fid is the session' (Clunk/Flush teardown, DMEXCL
instead of global busy flags, chatsrv/gpusrv exemplars) and the
'a 9P write can fail — check the reply' client rule, and the smells
table gains executing host-authored files at boot (config is data,
never code).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Eo29oZp7mPig1XekwRPsq

* docs: close the guidance corpus's host-boundary blind spot (counterfactual review)

A counterfactual audit tested the corpus against PR #421 (the
non-compliant voice PR that motivated the guidance): had the docs
existed, which defects would they have prevented? The architectural
class scored well; the consistent blind spot was every boundary with
the host. This closes the eight gaps it filed:

- emu-dev skill (new): kproc kill path (oshostintr interrupts syscalls,
  not condvar waits; osenter/osleave; never hold a QLock across a
  blocking wait), CONF/mkdevlist wiring (unwired driver = dead code),
  the incumbent rule, drivers-deliver-events layering.
- DESIGN-PRINCIPLES: 'The host boundary' section (pin + checksum
  everything fetched; installers never build-and-execute unpinned HEAD;
  boot never executes host-authored files; placement is shipping via
  the release copy loop); 'Writes are RPCs — on both sides'; smell rows
  for global busy flags and UI policy in drivers.
- LIMBO-FOR-GO-PROGRAMMERS: null-list concatenation and
  failed-redirection rows hoisted into the shell table as the single
  lookup point.
- Tutorial: caller-side dual of failed-at-the-writer; fid-is-the-session
  pointer on the blocking-reads bullet.
- limbo-test: norms — behavior not source text, a suite that skips in
  CI guards nothing, the contract test is the durable 'I ran it'.
- limbo-dev: pre-image hygiene before opening a PR.
- Maintainer ruling applied: dis/tests/*.dis is TRACKED — stale
  gitignore line dropped, CLAUDE.md/limbo-dev/limbo-test now agree.
- Maintainer ruling applied: sketch-first is policy — CONTRIBUTING
  states interface PRs without a proposal issue are returned; the
  style-gate workflow adds a soft proposal-link annotation; PR template
  gains the pinned-and-verified row; style gate documents its
  style-not-semantics scope.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Eo29oZp7mPig1XekwRPsq

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@Ticed

Ticed commented Aug 25, 2026

Copy link
Copy Markdown
Author

Superseded. This branch grew into one 64-file draft that is now conflicting with master, and it is not reviewable in that shape.

I have split it. Open so far, each a single logical change:

The speech service itself is the rest of it, and it introduces a new file interface, so it follows a namespace sketch rather than arriving as a PR. Closing this one so it stops showing up as pending review.

@Ticed Ticed closed this Aug 25, 2026
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.

2 participants