Proposal: consume the shipped MEAI speech interface for local Whisper transcription
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
Research direction
Read Audio\SpeechToTextService.cs and Services\VoiceService.cs first, then inspect SpeechToTextServiceTests.cs and SpeechToTextNativeProofTests.cs. Run the focused STT and lifecycle suites and review the validation guide. Done means maintainers have decided whether the bounded MEAI integration and asynchronous ownership prerequisite fit the retained contracts, with unresolved build limitations documented.
Written by the indexing model from the issue text.
Description
SpeechToTextService currently builds a Whisper processor directly and maps its segments into OpenClaw results. Whisper.net 1.9.0 already ships an ISpeechToTextClient adapter and already brings Microsoft.Extensions.AI.Abstractions transitively. There is an opportunity to consume that standard speech boundary without replacing Whisper, changing the model, or moving capture and voice-activity detection out of OpenClaw.
I implemented a bounded pilot on my fork's proposal/meai-whisper-stt branch. It reuses the shipped adapter, explicitly pins Abstractions 10.9.0, and preserves ordered segment text and timestamps instead of flattening the response. A separate prerequisite addresses model ownership across inference and asynchronous lifecycle calls.
This is a proposal for discussion, not a merge-ready PR. Focused and required test suites pass, and a small native comparison matches the original behavior exactly on one pinned public fixture. The full repository build remains blocked by an existing npm dependency download failure. This does not claim better transcription quality, faster inference, live microphone/UI correctness, or a Talk Mode fix.
Why this boundary?
The benefit is a standard, testable speech-client boundary while keeping the existing local provider. OpenClaw can exercise conversion, mapping, cancellation, and ownership against a controlled client without creating another Whisper adapter. This is not a new cloud routing layer or a promise that arbitrary MEAI providers have interchangeable streaming semantics.
| File | Proposed change |
|---|---|
OpenClaw.Shared.csproj |
Explicitly reference Microsoft.Extensions.AI.Abstractions 10.9.0. Keep Whisper.net 1.9.0 and Whisper.net.Runtime 1.9.1. |
Audio\SpeechToTextService.cs |
Consume the shipped WhisperSpeechToTextClient through ISpeechToTextClient; retain eager factory ownership and map streaming updates to existing results. |
Services\VoiceService.cs |
Await model loading and disposal through the lifecycle prerequisite. |
SpeechToTextServiceTests.cs |
Deterministic conversion, options, mapping, failure, cancellation, serialization, ownership, and synchronization-context cases against the actual service. |
SpeechToTextNativeProofTests.cs |
An opt-in offline comparison against an independent frozen copy of the original processor path. No automatic asset download or transcript output. |
The stable Abstractions package does not mean stable speech APIs: the contracts remain experimental (MEAI001). Suppression is local to the speech service and its two direct speech-API test files. The actual Shared, WinUI, Shared.Tests, Tray.Tests, and WinNode.Cli.Tests restored graphs resolve Abstractions 10.9.0, Whisper.net 1.9.0, and Whisper.net.Runtime 1.9.1. Abstractions adds no package dependencies for the actual .NET 10 target group.
The shipped adapter at the 1.9.0 source revision, rather than development main, determines the integration:
- Each streaming update contains one complete Whisper segment, despite its
TextUpdatingkind.GetTextAsyncwould flatten boundaries, so the service usesGetStreamingTextAsync. SpeechLanguagereceives the existing normalized two-letter language orauto; threads remainmax(1, CPU count / 2).TextLanguagestays unset because this adapter enables translation for any nonempty target language.GetServicethrowsNotImplementedException. The service never depends on it. The native probe records that limitation without requiring it to remain broken; broader metadata/middleware reuse needs an upstream provider fix.- The adapter may end enumeration normally after cancellation. Checks before, during, and after enumeration prevent a canceled partial utterance from becoming a successful result.
See also the Abstractions 10.9.0 package and immutable MEAI source. No duplicate adapter, middleware, exporter, cloud fallback, ML.NET/ONNX engine replacement, or provider registry was added.
Contracts retained
The service still accepts 16 kHz mono float[], applies the original clamp/PCM16 conversion, and creates the same WAV input. It trims nonempty segments while preserving order and exact start/end times. Returned Language retains its normalized-request meaning, including auto; it is not changed to detected language.
OpenClaw continues to own model downloading/readiness, microphone capture, resampling, VAD, utterance boundaries, permissions, and sanitized command errors. stt.transcribe retains its bounded capture of at most 30 seconds; stt.listen keeps its default 30 seconds and 1-120 second range; stt.status retains its privacy-safe readiness and engine labels. There is no MCP/gateway transport or schema change.
The service owns and disposes its generated WAV stream after enumeration. The adapter must leave caller-owned streams open; the native probe checks that separately. Streaming segment updates are not evidence of live bidirectional speech support.
Separate prerequisite: asynchronous model ownership
The original service serialized transcriptions but not load, unload, or disposal. A lifecycle operation could dispose the factory while inference used it. This is a preexisting lifetime hazard, not a defect attributed to MEAI.
Simply adding a blocking semaphore wait to synchronous APIs would introduce a UI-stall risk: VoiceService.InitializeAsync is reached from the voice overlay, and disposal is reached from settings unload/shutdown. Existing pipeline draining does not prove every inference is quiescent; agent-local pipelines are not necessarily the main pipeline being drained.
The separate prerequisite commit adds asynchronous lifecycle admission on the existing gate and awaits the two owned production call sites. It does not rewrite capture, VAD, draining, or shutdown orchestration.
| Case | Pilot behavior |
|---|---|
| Lifecycle requested during inference | The async caller receives a pending task. Model/client disposal waits for active enumeration and processor cleanup to finish. |
| Legacy synchronous lifecycle call while busy | Immediate explicit error before ownership/state changes. Idle synchronous use remains. This is an intentional concurrency-contract change, not completely behavior-neutral. |
| Queued reload/unload canceled | Current model stays owned and ready; cancellation is preserved. |
| Loaded model never transcribed | The service retains and cleans the eager factory before the lazy adapter has used it. |
| Client creation or disposal fails | Model cleanup is attempted. A secondary cleanup failure is logged without masking the primary client failure. |
The client is disposed before the separately retained eager model. The pinned factory's disposal is idempotent, so both cleanup paths free its native resource once. Derived-synchronization-context tests cover off-caller admission and controlled contention; these are not a demonstration of visible WinUI responsiveness.
Shutdown remains unbounded by this service: async disposal can await the rest of an uncanceled native inference. There is no new timeout or bounded-shutdown guarantee.
Validation and provenance
The combined source commit is 15134f93, based on 273b0182745a3093c0e09f306ca8a1fff6ef3c5a. The evidence-only head retains identical production and test source.
Tests ran on the combined worktree before those commits were created. All six final source-file Git blobs were verified against the committed tree. The machine-readable evidence identifies actual executed assemblies, package hashes, original TRX hashes, case inventories, and native metadata. The lifecycle-only intermediate commit was not independently executed.
Windows x64, SDK 10.0.401, Debug, xUnit 2.9.3, VSTest:
| Check | Total | Executed / passed | Failed | Skipped |
|---|---|---|---|---|
| Focused STT, lifecycle, language, capability and MCP contracts | 136 | 136 | 0 | 0 |
| Opted-in native proof | 1 | 1 | 0 | 0 |
| Full Shared suite | 4171 | 4138 | 0 | 33 |
| Full Tray suite | 3072 | 3072 | 0 | 0 |
| Full WinNode CLI suite | 127 | 127 | 0 | 0 |
All five final invocations exited zero and produced nonempty TRX reports whose case outcomes and counters agree. Do not sum overlapping rows as unique coverage. Shared's 33 ordinary skips are 16 DeviceIdentity, nine LocalCommandRunner, seven MXC integration cases, and the one native Whisper case that passed separately. Native opt-in variables were cleared before ordinary suites.
The evidence guide maps requirements to exact test names and reproduction commands. The new deterministic cases cover independent expected WAV bytes/options, ordered segment mapping, empty segments, queued/in-flight cancellation with controlled fakes, no partial success, sanitized errors, single-flight reuse, disposal ordering, and primary-error preservation. The real VoiceService awaiting check is a source guard plus qualified compilation, not an executed UI lifecycle test.
The record orders 16 retained build/test commands with their actual exit codes. Twelve have original TRX runner timestamps. Exact shell/build/restore boundaries were not retained and are explicitly unavailable; runner timestamps are not presented as those boundaries.
The single required full-build attempt failed fetching the existing locked node-pty-1.2.0-beta.12.tgz from npm with ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE. It was not retried or bypassed. Documentation validation and Shared/Cli/WinNodeCli/SetupEngine builds succeeded; the full build did not.
A subsequent scoped WinUI build compiled the actual changed VoiceService, then failed copying absent wxc-exec.exe. Existing Compile, VC-runtime, and setup-asset copy targets subsequently exited zero with SkipMxcNodeBridgeRestore=true. The inspected output was bin\Debug\...\win-x64, with Platform omitted. This is qualified compile/harness evidence, not shipping, MXC, app-launch, or full-build success.
An intermediate test-helper accessibility error was corrected and all final suites rerun. Earlier passing focused/native runs and the failed full build remain attached to the earlier source patch, not relabeled as final-source proof. Complete early untracked test snapshots and early assembly hashes were not retained; the final evidence supersedes those runs.
Native comparison
The native test independently freezes the original 273b018 PCM conversion, processor construction, language handling, and segment mapping. It does not call candidate conversion or mapping helpers. Both paths receive identical samples, model/runtime, thread count, and language options.
The approved public assets are ggml-tiny.bin at ggerganov/whisper.cpp revision 5359861c739e955e79d9a303bcbc70fb988958b1 and samples/jfk.wav at ggml-org/whisper.cpp revision d09f61a708f3487afa956ff578e60eae5e7a233c. The guide records their provenance, sizes, SHA256s, and the fixture's Git-blob check, including its Git header. Assets are not downloaded automatically by tests.
Both paths received 176,000 samples (11 seconds), float-byte SHA256 ebd52851100536db02d12c49fddd010372dcdc70243562e057553d476b706ae0. Actual loaded backend was CPU, with eight configured threads. The test enumerated loaded modules and recorded hashes for whisper.dll, ggml-whisper.dll, ggml-base-whisper.dll, and ggml-cpu-whisper.dll; backend identity was not inferred from settings or available files.
Hashes cover complete ordered text, start/end timestamp ticks, and language:
| Requested / normalized language | Repetitions | Baseline and candidate hash in both repetitions |
|---|---|---|
en-US / en |
2 | ebec48e0cacf88c1fb0bd913e4ea1bb18569ca219e68ce84dad0fe16f2e99a0c |
auto / auto |
2 | 8bc1d3093b9c0614e143c2f3256bfb80ab6e73335a0e3d62bdcbdd75748144df |
All four baseline/candidate pairs and both repeat comparisons matched exactly, without relaxed tolerances. Each produced one segment. Native multi-segment corpus behavior remains unverified; deterministic tests cover that mapping.
The native probe also checks caller-stream ownership, unused-model reload/unload, and pre-canceled admission with actual token identity. Its output explicitly says cancellationScope=preCanceledAdmissionOnly; nativeMidFlightAbort=unverified. Controlled fakes establish the service's in-flight cancellation behavior, not native mid-flight abort. Durations are diagnostic, not benchmarks.
No model/audio binaries, transcript payloads, raw test logs, private paths/settings, or credentials are published.
Implementation and acceptance path
- Review the lifecycle prerequisite independently. Keep the existing owner/gate, await owned callers, retain the explicit busy-sync policy, and avoid widening capture/drain scope.
- Add the Abstractions pin and consume the shipped adapter. Preserve eager factory ownership, normalized
SpeechLanguage, unchanged threads/WAV bytes, and unsetTextLanguage. - Map the pinned adapter's complete streaming segments without dropping timestamps/order. Retain token checks around enumeration and sanitized capability failures. Do not require unavailable metadata or introduce middleware as part of this change.
- Bring the deterministic tests and opt-in independent native oracle with the change. Follow the guide's exact filters and asset checks in an isolated worktree/settings directory; retain exact hashes, repetitions, nonzero execution counts, and all existing STT/MCP/CLI tests.
- Resolve the full-build environment blocker and rerun required checks before a merge-ready PR. Collect real UI lifecycle behavior and native ARM64 parity separately.
Acceptance should preserve the local engine/model, options, result shapes, readiness, and permission behavior; demonstrate safe ownership under cancellation/reload/disposal; and reject canceled partial success. Full packaging, native abort/shutdown bounds, provider interchangeability, and live bidirectional speech require separate proof.
Alternatives and remaining scope
Keeping the concrete Whisper processor is reasonable if the experimental API and unavailable metadata outweigh the value of the standard boundary. The lifecycle prerequisite can also be considered independently of MEAI adoption, although that intermediate commit still needs its own execution evidence. A small application-specific test seam is another option, at the cost of maintaining a separate contract. There is no need to replace a working local engine merely to adopt an abstraction.
Live UI/microphone/capture/drain behavior, MCP/gateway round trips, ARM64, transcription quality, and application performance remain unverified. The applicable future proof pools are windows-winui-interactive and windows-11-arm64; neither was completed here.
Upstream main was 5a59535216eea603192f2df13d4eddf1e1a20267 at closeout, three commits beyond the retained base. Those changes do not alter this speech service, its VoiceService callers, or the Shared package manifest, but this is not a full-current-main validation claim.
This is adjacent to, not a fix for, #1087, #1124, or #1097. The experimental Abstractions 10.9.0 pin aligns with the separate Windows TTS proposal, #1528; these independent implementations have not been combined or tested together.
I can put up a PR if there is interest.
- Dominant language
- C#
- Stars
- 2.1k
- Forks
- 315
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 127
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- No contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from openclaw/openclaw-windows-node
-
clawsweeper:source-repro impact:ux-release-blocker issue-rating: 🦞 diamond lobster P0
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
openclaw/openclaw-windows-node#1723 · 1 comment · 1 reaction ·
Maintainers usually reply within 1 day
-
clawsweeper:needs-maintainer-review clawsweeper:needs-product-decision clawsweeper:no-new-fix-pr clawsweeper:source-repro impact:security issue-rating: 🦞 diamond lobster P2
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
openclaw/openclaw-windows-node#1516 · 1 comment · 1 reaction ·
Maintainers usually reply within 1 day
-
clawsweeper:needs-info clawsweeper:needs-maintainer-review clawsweeper:no-new-fix-pr impact:ux-friction issue-rating: 🦐 gold shrimp P1 stale
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
openclaw/openclaw-windows-node#1145 · 5 comments · 1 reaction ·
Maintainers usually reply within 1 day
-
clawsweeper:needs-security-review clawsweeper:no-new-fix-pr clawsweeper:source-repro impact:security issue-rating: 🦞 diamond lobster P1
Difficulty 3/5 1-2 days Newbie friendliness 35/100
openclaw/openclaw-windows-node#1736 · 1 comment · 1 reaction ·
Maintainers usually reply within 1 day
-
clawsweeper:needs-live-repro clawsweeper:needs-maintainer-review clawsweeper:no-new-fix-pr impact:ux-release-blocker issue-rating: 🐚 platinum hermit P0
Difficulty 4/5 3-5 days Newbie friendliness 32/100
openclaw/openclaw-windows-node#1725 · 4 comments · 1 reaction ·
Maintainers usually reply within 1 day
All issues in openclaw/openclaw-windows-node
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
ready-for-agent size:S status:todo
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
elsa-workflows/elsa-foundation#2830 ·
Maintainers usually reply within 1 day
-
Review - Maintainer WG-Maintainers-Build
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
PowerShell/PowerShell#28220 · 1 comment ·
Maintainers usually reply within 1 day
-
area: docs eco: dotnet good first issue help wanted lane: free-agent roadmap size: XS type: docs
Difficulty 2/5 Under an hour Newbie friendliness 72/100
arminazimi/evergreen#377 ·
Maintainers usually reply within 1 day
-
documentation good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day