Version. 0.2
Date. 2026-05-20
Author. Claude.ai (this conversation)
Working machine. The Mac Mini, with Claude Code as the executor
Repository. DUNIN7/loomworks-engine (substrate, small) and DUNIN7/loomworks (frontend, primary)
Substrate baseline. loomworks-engine main at tag voice-provenance-v0_1 (assumed; the voice-provenance ship lands first).
Frontend baseline. loomworks branch phase-60-operator-layer-upload-pathway-v1 at tag voice-provenance-v0_1 (assumed).
Grounded in.
voice-listening-v0_1 (engagement-navigation) and in-engagement-voice-listening-v0_1 (in-engagement).This change-request adds a silence-detection submit path to the voice listening hook, in addition to the existing explicit "say 'send' to submit" path.
Today, voice listening on the in-engagement surface (and on engagement-navigation, parallel) requires the Operator to say the word "send" at the end of their utterance to commit the transcript. Without "send", the Web Speech API's built-in silence timeout fires and the transcript is discarded (handled as a cancel).
This works well for deliberate dictation — long thoughts, multiple sentences, pauses for thinking, then explicit close with "send". It fails for short conversational utterances like "Companion, where did I park?" where the Operator's natural speech rhythm has them stop talking when they're done. Today they have to remember to say "send" or they lose the transcript.
After this ships:
voice.listening.silence_submit_seconds lives in person_settings. Default 2.5. Special value 0 disables silence-submit (revert to explicit-send-only behavior — for Operators who prefer the explicit-only model).What you (the Operator) see after this ships.
Open an engagement. Click the microphone. Say "Rico, where did I park?" (or "Companion, where did I park?" if you haven't renamed your Companion, or just "where did I park?" without addressing it at all) — stop talking. After ~2.5 seconds of silence, the panel closes, the transcript commits as your turn, the Companion replies. No need to say "send".
The voice infrastructure doesn't listen for the Companion's name — it captures whatever you say and routes it as a turn. Whether you address the Companion explicitly (by default name "Companion", or by whatever custom name you've given it like "Rico"), or just speak the question directly without an addressee, the transcript flows the same way.
For a longer dictation, say a paragraph, pause mid-thought (1 second is fine), continue, pause again, finish. Say "send" to explicitly close — that submits immediately, no waiting for the silence threshold. The "send" override still works.
For Operators who prefer the explicit-only model, say "don't wait, only send on 'send'" to the Companion — the silence_submit_seconds setting goes to 0, behavior reverts to today's.
For shorter or longer thresholds, say "wait three seconds before sending" (or any value in seconds between 0.5 and 10) — the setting tunes accordingly.
What this change-request does not do.
Estimated effort. Two checkpoints — Section A small substrate work (new setting key + tune_setting vocabulary extension), Section C primary frontend work (extend the useVoiceListening hook with silence detection + wire the threshold + update panel footer text).
Methodology inheritance. Companion-tunable-settings-as-Companion-said-not-menu-clicked — the silence threshold is adjusted by speaking to the Companion. Loomworks-pattern-over-imported-convention — voice as a thinking-surface interaction supports both deliberate-with-explicit-close and conversational-with-natural-close rhythms; the choice isn't imported from a specific assistant convention. Thinking-surface-not-messaging-surface — voice on a thinking surface needs to handle both rhythms because thinking spans them.
ls -la /Users/dunin7/loomworks-engine
ls -la /Users/dunin7/loomworks
cd /Users/dunin7/loomworks-engine && git status
cd /Users/dunin7/loomworks && git status
git -C /Users/dunin7/loomworks-engine log --oneline -3
git -C /Users/dunin7/loomworks log --oneline -3
Both working trees clean. Substrate at voice-provenance-v0_1 or later. Frontend at the same.
Confirm the current shape of the voice listening hook:
cd /Users/dunin7/loomworks
cat src/app/operator/engagement-navigation/useVoiceListening.ts | head -80
Report:
onend / onerror events.The silence-submit work hooks into the interim-transcript-update path — each interim transcript event resets a silence timer; when the timer fires without further interim events, silence-submit triggers.
Confirm the handler's executor dispatch and existing setting keys:
cd /Users/dunin7/loomworks-engine
grep -n "_execute_enum_setting\|_execute_numeric_setting\|_execute_stepped_int_setting\|KNOWN_SETTINGS" src/loomworks/orchestration/tune_setting.py src/loomworks/preferences/person_settings.py | head -20
The handler currently has three executors:
_execute_enum_setting (for conversation.message_order)_execute_numeric_setting (for voice.listening.blur_intensity)_execute_stepped_int_setting (introduced and then retired in the FORAY-audit ship; the executor may or may not still exist in code — Claude Code verifies)
The new setting voice.listening.silence_submit_seconds is a numeric float (similar shape to blur_intensity but with different bounds and a meaningful 0-value). Either reuse _execute_numeric_setting with appropriate spec parameters, or add a small dispatch branch. Claude Code's call at execution.
Confirm where the listening panel's footer text is rendered:
cd /Users/dunin7/loomworks
grep -n "say.*send.*submit\|esc to cancel" src/app/operator/engagement-navigation/VoiceListeningPanel.tsx
The footer text is a hardcoded string today. This ship makes it conditional on whether silence_submit_seconds > 0.
If any check surfaces drift (especially around the _execute_stepped_int_setting retire — if it was retired, this ship may need it re-introduced for numeric float settings with floor/ceiling like silence_submit_seconds), halt and report. If everything matches, proceed to Section A.
KNOWN_SETTINGS in person_settings.py gains voice.listening.silence_submit_seconds:
2.5[0.0, 10.0] (0 = disabled; 0.5 minimum to avoid trigger-happy submission; 10.0 maximum to avoid effectively-never)_validate_silence_submit_seconds enforces the bounds and the float type; rejects bool, NaN, negative, out-of-range.
_LABEL_TO_KEY gains:
voice.listening.silence_submit_secondsDirection vocabulary on this setting:
The numeric-parsing branch is the new shape — prior settings (blur intensity, message order, banner duration) didn't accept arbitrary numeric values from the Operator; they used named direction tokens. Voice silence threshold benefits from explicit numeric input because Operators often have a specific feel for what duration works for them. The parser accepts patterns like "N seconds", "N second", "N.N seconds", "N", "N.N" — extracted from the operation_data direction string. If parsing fails or value is out of bounds, fall back to no-change with a Companion-spoken explanation.
The plain-words renderer handles the new key:
0 → "disabled — voice only submits when you say 'send'"0.5–9.5 → "{N} seconds of silence before submitting" (1 second special-cased to singular)10 → "10 seconds of silence before submitting" (maximum)Per-write tests:
Approximately ten to fifteen new pytest tests.
intent_classifier.md gains:
tune_setting for silence-submit phrasings
tune_setting.md adds the new setting's deterministic message templates (per the existing pattern):
voice.listening.silence_submit_seconds:
No new Companion handler. The existing tune_setting handler's vocabulary extends in Section A. Section deliberately empty.
The hook extends with a silence-detection mechanism:
voice.listening.silence_submit_seconds from person_settings (via the existing fetchMeSetting adapter). Hold the value in a ref so the timer logic can read it without re-running effects on every state change.silence_submit_seconds === 0 (disabled), no timer is started; behavior reverts to today's explicit-send-only model.onend event (which fires on its own internal timeout) is handled per current logic — if no transcript was collected, it's a cancel; if transcript exists but neither "send" nor silence-submit fired (edge case where the API closes faster than the configured silence threshold), the existing behavior is preserved.Each time the Operator opens the listening panel (microphone click → start listening), the hook re-reads the silence_submit_seconds setting. This matches the blur intensity pattern — the setting is read fresh on each open so Companion-spoken changes take effect on the next listening session.
VoiceListeningPanel.tsx's footer text becomes conditional:
silence_submit_seconds > 0: "say 'send' or pause to submit · esc to cancel"silence_submit_seconds === 0: "say 'send' to submit · esc to cancel" (current)The footer reads the current setting (passed as a prop from the hook) and renders accordingly.
The panel does not show a visible countdown of "submitting in 2.5s…". The silence-submit fires when the threshold elapses; the panel just closes. Operators who want explicit control use "send"; those who let silence resolve get a clean implicit close.
(Rationale: a visible countdown would clutter the panel and might invite the Operator to keep talking just to "reset the timer" mid-thought. The submit happens or it doesn't; if the Operator wants explicit control, "send" is always available.)
Vitest unit tests cover:
silence_submit_seconds === 0 disables silence-submit entirely; no timer starts.Approximately ten to fifteen new vitest tests.
Checkpoint A — substrate setting key + handler extension.
cd /Users/dunin7/loomworks-engine
pytest tests/test_voice_tune_setting* tests/test_voice_silence* -v
pytest
New tests pass; full suite shows no regressions beyond the two pre-existing baseline failures.
Halt for Operator review.
Checkpoint C — frontend hook + panel.
cd /Users/dunin7/loomworks
npm test
npm run build
All existing vitest tests plus the new tests pass. Build succeeds.
Smoke test. Operator opens an engagement. Confirms:
```
psql -h localhost -U playground -d playground_dev -c "SELECT timestamp, payload->>'setting_key' AS key, payload->>'previous_value' AS prev, payload->>'new_value' AS new FROM audit.foray_events WHERE payload->>'setting_key' LIKE 'voice.listening.silence%' ORDER BY timestamp DESC LIMIT 5;"
```
Press q to exit pager. Recent silence-threshold changes should appear as audit rows.
Halt for Operator visual confirmation.
voice.listening.silence_submit_seconds persists in person_settings; default 2.5, special value 0 disables silence-submit.Honored. The silence threshold is adjusted by speaking to the Companion ("wait longer", "wait 3 seconds", etc.). No menu surface is added.
Honored. Voice as a thinking-surface interaction supports both deliberate (explicit-send) and conversational (natural-silence) rhythms. The choice isn't imported from a specific assistant convention.
Honored. Voice on a thinking surface needs to handle both rhythms because thinking spans them — quick conversational asks ("Companion, where did I park?") and longer deliberate compositions both deserve clean submission paths.
Honored. The default 2.5 seconds is a reasonable starting point that most Operators will accept; those who prefer different timing or the explicit-only model can tune via Companion.
Honored. The new setting changes write FORAY audit rows via the existing _audit_setting_change mechanism. Adds another narrative event type to the substrate's audit ledger.
Approved for execution.
- Marvin Percival
- [timestamp]
Once approval is recorded, Claude Code executes end-to-end, halting at Checkpoints A and C.
DUNIN7 — Done In Seven LLC — Miami, Florida
Voice listening silence-submit change-request — v0.2 — 2026-05-20
Grounded in the voice-listening-timeout-reset follow-on filed during the engagement-list scoping ship and the Companion-tunable-settings methodology candidate
Builds on voice-provenance-v0_1 (assumed prior ship)
Requires Operator approval before Claude Code begins execution.