Summary
Asking the realtime voice agent to summarise emails returns no summary. The final answer delivered to chat is the agent announcing that it is about to fetch the inbox.
Problem
What happened. Asked the voice agent "summarize my emails". The final response delivered to the chat thread was:
"Let me pull up your inbox now — if it takes a moment I'll drop the summary in your chat."
No inbox summary ever arrived, in chat or spoken.
What was expected. The inbox summary.
Why it happens. That sentence is the VOICE_DIRECTIVE's own example, echoed almost verbatim:
"Sure, let me pull up your inbox — if it takes a moment I'll drop the summary in your chat."
The directive instructs the model to speak before acting:
"Before you use a tool or delegate … first say one short spoken sentence telling the user what you are doing … then proceed."
But a reply carrying only text and no tool call is the end of a turn. A model that follows the instruction emits the sentence, its turn ends there, and the harness delivers that sentence as the final answer. The Gmail call never runs. The more obediently the model complies, the more reliably the turn fails.
Impact. Any voice request that needs a tool — email, calendar, files, web — can silently return a promise instead of an answer. The failure is invisible: the user is told the result is coming, so nothing reads as broken until they notice it never arrived.
Steps to reproduce.
- Start a realtime voice chat on the Human tab.
- Ask something that requires a tool ("summarize my emails").
- Observe the chat thread: the delivered answer is the announcement, not the summary.
Environment. Desktop dev build, macOS, realtime voice path (src/openhuman/voice/realtime_harness.rs).
Solution
Remove the preface clause and instruct the opposite: call the tool and answer from the result.
The clause existed for a latency reason that no longer holds. It was there to get audio to the caller early, back when the relay's spoken filler was inaudible until the turn closed — the provider segments on sentence boundaries, and the filler was ellipsis-terminated (tinyhumansai/backend#1275). The relay now speaks a terminated sentence ~700ms in, and does so without depending on the model choosing to speak first.
The replacement states the reason as well as the rule, since a bare prohibition invites the model to hedge back into the same behaviour.
Acceptance criteria
Related
Summary
Asking the realtime voice agent to summarise emails returns no summary. The final answer delivered to chat is the agent announcing that it is about to fetch the inbox.
Problem
What happened. Asked the voice agent "summarize my emails". The final response delivered to the chat thread was:
No inbox summary ever arrived, in chat or spoken.
What was expected. The inbox summary.
Why it happens. That sentence is the
VOICE_DIRECTIVE's own example, echoed almost verbatim:The directive instructs the model to speak before acting:
But a reply carrying only text and no tool call is the end of a turn. A model that follows the instruction emits the sentence, its turn ends there, and the harness delivers that sentence as the final answer. The Gmail call never runs. The more obediently the model complies, the more reliably the turn fails.
Impact. Any voice request that needs a tool — email, calendar, files, web — can silently return a promise instead of an answer. The failure is invisible: the user is told the result is coming, so nothing reads as broken until they notice it never arrived.
Steps to reproduce.
Environment. Desktop dev build, macOS, realtime voice path (
src/openhuman/voice/realtime_harness.rs).Solution
Remove the preface clause and instruct the opposite: call the tool and answer from the result.
The clause existed for a latency reason that no longer holds. It was there to get audio to the caller early, back when the relay's spoken filler was inaudible until the turn closed — the provider segments on sentence boundaries, and the filler was ellipsis-terminated (tinyhumansai/backend#1275). The relay now speaks a terminated sentence ~700ms in, and does so without depending on the model choosing to speak first.
The replacement states the reason as well as the rule, since a bare prohibition invites the model to hedge back into the same behaviour.
Acceptance criteria
.github/workflows/ci-lite.yml).Related