Skip to content

voice-agent: gate googleSearch on VOICE_GOOGLE_SEARCH env var (3.1 compat) - #262

Merged
sonichi merged 2 commits into
mainfrom
fix/voice-agent-3.1-googleSearch-gate
Apr 10, 2026
Merged

sonichi merged 2 commits into
mainfrom
fix/voice-agent-3.1-googleSearch-gate

Conversation

@sonichi

@sonichi sonichi commented Apr 10, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the second voice-agent-specific Gemini 3.1 compat issue discovered right after the 3-PR compat stack landed (bodhi #2 + bodhi #3 + sutando #259).

Reproduced this evening

After merging all 3 stack PRs + `npm install github:sonichi/bodhi_realtime_agent`, voice-agent on 2.5 worked fine. Flipping `.env` to `gemini-3.1-flash-live-preview` still hit:

```
03:13:25 [VoiceSession] Transport closed (state=CONNECTING code=1011
reason="You exceeded your current quota, please check your plan and
billing details. For more information on this error, head to: h...")
```

Same misleading "quota" error as yesterday's duplicate-tool 1011 (the text is wrong — it's not actually a billing issue, Gemini 3.1 just reuses that error code for unrecognized-config rejections that 2.5 silently accepted).

Root cause

Isolated via A/B: flip `googleSearch: true → false` in the voice-agent `mainAgent` config, keep everything else identical (.env still pinned to 3.1, same installed bodhi, same tool list). Result: setup complete in 315ms, no 1011. Flip back to `googleSearch: true` + 3.1 — 1011 returns.

Gemini 3.1 native audio rejects the `googleSearch` grounding tool entry. 2.5 silently accepted the same config.

Fix

Env-var gate:
```ts
const VOICE_GOOGLE_SEARCH = (process.env.VOICE_GOOGLE_SEARCH ?? 'true').toLowerCase() !== 'false';
```

  • Default `true` — preserves existing 2.5 behavior, zero config change required for current users
  • Set `VOICE_GOOGLE_SEARCH=false` in `.env` when unpinning `VOICE_NATIVE_AUDIO_MODEL` to `gemini-3.1-flash-live-preview`

A lengthy comment at the declaration documents the constraint and points at this investigation so the next reader doesn't have to re-discover it.

Trade-off

Voice-agent loses google search grounding on 3.1. Google search was used for "quick factual lookups" per the system instructions. 3.1 users will need to route those through the `work` tool (sutando-core delegates to Claude Code with google search available there). Acceptable — the sutando-core path is already the standard for anything non-trivial.

Test plan

  • `tsc --noEmit` clean
  • Default (`VOICE_GOOGLE_SEARCH` unset, 2.5 pinned): `Gemini setup complete` at 03:20:46, googleSearch: true as before
  • Investigation branch with googleSearch hardcoded false + `.env` flipped to 3.1: `Gemini setup complete` at 03:18:48 — no 1011
  • Live session test on 3.1 (user action): set `VOICE_GOOGLE_SEARCH=false` + `VOICE_NATIVE_AUDIO_MODEL=gemini-3.1-flash-live-preview`, restart voice-agent, run a real session (greeting / tool call / voice goodbye / reconnect)

Follow-up idea (deferred)

Auto-infer `googleSearch=false` when `VOICE_NATIVE_AUDIO_MODEL` contains `3.1`. Would remove the need to set two env vars in lockstep. Not doing it here because the explicit config is more discoverable and easier to revert if we later discover other 3.1-version-specific quirks. Open to doing it as a follow-up if you'd prefer.

Related

🤖 Generated with Claude Code

Isolates the second voice-agent-specific 3.1 compat issue discovered
while landing the Gemini 3.1 stack today. Chi's #259 (dedup) +
Susan's bodhi #2 (sendAudio) + my bodhi #3 (sendFile) all merged,
npm installed, and 2.5 continued to work fine — but flipping .env to
gemini-3.1-flash-live-preview still hit 1011 "exceeded your current
quota" within 200ms of WebSocket setup. Misleading error text; the
actual cause turned out to be the googleSearch + native-audio combo.

Experiment: flipped googleSearch: true → false on an investigation
branch, kept everything else identical (.env to 3.1, same dist,
same tool list). Result: setup complete in 315ms, no 1011. Flipped
back to googleSearch: true + 3.1 and the 1011 returned. Reproducible.

So: Gemini 3.1 native audio rejects the googleSearch grounding
tool entry. 2.5 silently accepted it. The rejection manifests as
the same misleading "quota" close code that caught us on #259's
duplicate-declaration bug earlier.

Fix: env-var gate. VOICE_GOOGLE_SEARCH defaults to 'true' to
preserve existing 2.5 behavior. Users unpinning VOICE_NATIVE_AUDIO_MODEL
to 3.1 set VOICE_GOOGLE_SEARCH=false in .env at the same time.
Comment at the declaration explains the constraint and points at
today's investigation.

Voice-agent LOSES google-search grounding on 3.1. Google search was
used for "quick factual lookups" per the system instructions; 3.1
users will need to route those queries through the work tool
(sutando-core) instead. Acceptable trade-off.

Test plan:
- ✓ tsc clean
- ✓ Default (VOICE_GOOGLE_SEARCH unset, 2.5 pinned): setup complete,
  googleSearch: true as before
- ✓ Investigation: VOICE_GOOGLE_SEARCH=false + 3.1 pinned → Gemini
  setup complete in 315ms, no 1011
- Live: user needs to test the full 3.1 session (greeting, tool
  call, voice goodbye, reconnect) before we can call 3.1 "ready"

Follow-up: consider auto-inferring googleSearch=false when
VOICE_NATIVE_AUDIO_MODEL contains "3.1" — would remove the need
for users to set two env vars together. Not doing it here because
the explicit config is more discoverable and easier to revert.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
sonichi added a commit that referenced this pull request Apr 10, 2026
Extends the script to enforce the second 3.1 compat requirement
discovered in #262: VOICE_GOOGLE_SEARCH must be 'false' when
VOICE_NATIVE_AUDIO_MODEL is pinned to 3.1. Without it, voice-agent
hits a misleading 1011 close on connect.

Check matrix:
  2.5 + default (googleSearch=true)   → pass
  2.5 + VOICE_GOOGLE_SEARCH=false     → warn (unnecessarily losing search)
  3.1 + VOICE_GOOGLE_SEARCH=false     → pass
  3.1 + default (googleSearch=true)   → fail (1011 on connect)
  3.1 + unset VOICE_GOOGLE_SEARCH     → fail (defaults to true → 1011)

Also updates the 'Manual next steps' tail to tell users to set BOTH
env vars together when flipping to 3.1.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
sonichi added a commit that referenced this pull request Apr 10, 2026
Fixes a false positive in the voice-transport probe (#258). Observed
live 2026-04-09 at 03:30: voice-agent sat idle for ~10 minutes, then:

  03:29:46 [VoiceSession] GoAway from Gemini (timeLeft=50s)
  03:30:46 [VoiceSession] Transport closed (state=ACTIVE code=1011
    reason="The service is currently unavailable.")
  03:30:46 [VoiceSession] Gemini disconnected — will reconnect fresh
    when client connects

That's Google's normal idle-timeout path: Gemini Live fires a GoAway
60s warning, then closes the transport with code=1011 and a
"service is currently unavailable" reason. The bodhi session
transitions to CLOSED and waits for the next client connect. When a
client comes back, `handleClientConnected` reconnects fresh. Not a
failure, just the expected lifecycle for an idle voice-agent.

The probe was flagging every 1011-after-idle as `fail` with
"unrecovered transport close" — which trains users to ignore the
signal exactly when we need them to trust it (real 1011s from #259
duplicate-tool or #262 googleSearch misconfig get lost in the noise).

Fix: track a `goaway_before_close` flag that flips true on GoAway
and false on setup complete. Abnormal closes while the flag is
true get reclassified as expected idle-timeouts (not flagged). The
flag also clears on setup complete so a RECOVERED idle timeout
followed by a fresh unrelated 1011 still correctly fails.

Test plan:
- ✓ Live probe on current log (has idle-timeout 1011 from 03:30):
  voice-transport ok
- ✓ 4-case simulated-log harness:
  1. GoAway → 1011 "service unavailable" unrecovered → ok (was fail)
  2. Unrecovered 1011 quota, no GoAway preceding → fail (as before)
  3. GoAway → 1011 → setup complete (reconnected) → ok
  4. Recovered idle-timeout + fresh 1011 with no new GoAway → fail
     (proves the flag correctly resets on setup complete)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
PR #262 branched before #259 merged, so the branch's package-lock
still had @google/genai@1.48 while package.json had 1.49 (inherited
from the rebase pull). CI failed with:

  npm error Invalid: lock file's @google/genai@1.48.0 does not
  satisfy @google/genai@1.49.0

Regenerated lockfile via `npm install` (no args). No other changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
sonichi added a commit that referenced this pull request Apr 10, 2026
)

Fixes a false positive in the voice-transport probe (#258). Observed
live 2026-04-09 at 03:30: voice-agent sat idle for ~10 minutes, then:

  03:29:46 [VoiceSession] GoAway from Gemini (timeLeft=50s)
  03:30:46 [VoiceSession] Transport closed (state=ACTIVE code=1011
    reason="The service is currently unavailable.")
  03:30:46 [VoiceSession] Gemini disconnected — will reconnect fresh
    when client connects

That's Google's normal idle-timeout path: Gemini Live fires a GoAway
60s warning, then closes the transport with code=1011 and a
"service is currently unavailable" reason. The bodhi session
transitions to CLOSED and waits for the next client connect. When a
client comes back, `handleClientConnected` reconnects fresh. Not a
failure, just the expected lifecycle for an idle voice-agent.

The probe was flagging every 1011-after-idle as `fail` with
"unrecovered transport close" — which trains users to ignore the
signal exactly when we need them to trust it (real 1011s from #259
duplicate-tool or #262 googleSearch misconfig get lost in the noise).

Fix: track a `goaway_before_close` flag that flips true on GoAway
and false on setup complete. Abnormal closes while the flag is
true get reclassified as expected idle-timeouts (not flagged). The
flag also clears on setup complete so a RECOVERED idle timeout
followed by a fresh unrelated 1011 still correctly fails.

Test plan:
- ✓ Live probe on current log (has idle-timeout 1011 from 03:30):
  voice-transport ok
- ✓ 4-case simulated-log harness:
  1. GoAway → 1011 "service unavailable" unrecovered → ok (was fail)
  2. Unrecovered 1011 quota, no GoAway preceding → fail (as before)
  3. GoAway → 1011 → setup complete (reconnected) → ok
  4. Recovered idle-timeout + fresh 1011 with no new GoAway → fail
     (proves the flag correctly resets on setup complete)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@sonichi
sonichi merged commit 0489afd into main Apr 10, 2026
1 check passed
@sonichi
sonichi deleted the fix/voice-agent-3.1-googleSearch-gate branch April 10, 2026 04:07
sonichi added a commit that referenced this pull request Apr 10, 2026
…261)

* Add src/verify-gemini-31.sh — pre-rollout gate for Gemini 3.1 stack

Scripts the post-merge verification after the 3-PR Gemini 3.1 compat
stack lands:
  - bodhi fork #1 (sendClientContent → sendRealtimeInput text path)
  - bodhi fork #2 (Susan: sendAudio media → audio)
  - bodhi fork #3 (me: sendFile mimeType branching)
  - sutando #259 (Chi: deduplicate tool declarations + SDK bump)

Checks the installed bodhi dist for each wire-format fix using
awk-scoped extraction (simpler grep would false-positive on the
OpenAI realtime transport's sendAudio which also uses `audio:` as a
flat field, so the 3.1 gemini-specific check has to be scoped to the
Gemini transport's function body).

Checks sutando src for the phone conversation-server's tool dedup
comment as a PR #259 marker.

Checks .env for the 2.5 pin (exits green on 2.5, warns on 3.1 — so
the script is safe to run repeatedly before, during, and after the
rollout).

Exits non-zero with "NOT READY for 3.1 rollout" if any wire-format
fix is missing. On success, prints the manual next-steps checklist
for unpinning .env, restarting voice-agent, and running the real
3.1 session test.

Usage:
  bash src/verify-gemini-31.sh              # check current state
  bash src/verify-gemini-31.sh --install    # npm install + check

Tested on the current state: correctly reports PR #1 applied,
PR #2 + #3 + #259 not yet. Will flip to "ready" once those merge
and an `npm install` pulls the updated bodhi SHA.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* verify-gemini-31: check VOICE_GOOGLE_SEARCH pin in .env

Extends the script to enforce the second 3.1 compat requirement
discovered in #262: VOICE_GOOGLE_SEARCH must be 'false' when
VOICE_NATIVE_AUDIO_MODEL is pinned to 3.1. Without it, voice-agent
hits a misleading 1011 close on connect.

Check matrix:
  2.5 + default (googleSearch=true)   → pass
  2.5 + VOICE_GOOGLE_SEARCH=false     → warn (unnecessarily losing search)
  3.1 + VOICE_GOOGLE_SEARCH=false     → pass
  3.1 + default (googleSearch=true)   → fail (1011 on connect)
  3.1 + unset VOICE_GOOGLE_SEARCH     → fail (defaults to true → 1011)

Also updates the 'Manual next steps' tail to tell users to set BOTH
env vars together when flipping to 3.1.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…onichi#263)

Fixes a false positive in the voice-transport probe (sonichi#258). Observed
live 2026-04-09 at 03:30: voice-agent sat idle for ~10 minutes, then:

  03:29:46 [VoiceSession] GoAway from Gemini (timeLeft=50s)
  03:30:46 [VoiceSession] Transport closed (state=ACTIVE code=1011
    reason="The service is currently unavailable.")
  03:30:46 [VoiceSession] Gemini disconnected — will reconnect fresh
    when client connects

That's Google's normal idle-timeout path: Gemini Live fires a GoAway
60s warning, then closes the transport with code=1011 and a
"service is currently unavailable" reason. The bodhi session
transitions to CLOSED and waits for the next client connect. When a
client comes back, `handleClientConnected` reconnects fresh. Not a
failure, just the expected lifecycle for an idle voice-agent.

The probe was flagging every 1011-after-idle as `fail` with
"unrecovered transport close" — which trains users to ignore the
signal exactly when we need them to trust it (real 1011s from sonichi#259
duplicate-tool or sonichi#262 googleSearch misconfig get lost in the noise).

Fix: track a `goaway_before_close` flag that flips true on GoAway
and false on setup complete. Abnormal closes while the flag is
true get reclassified as expected idle-timeouts (not flagged). The
flag also clears on setup complete so a RECOVERED idle timeout
followed by a fresh unrelated 1011 still correctly fails.

Test plan:
- ✓ Live probe on current log (has idle-timeout 1011 from 03:30):
  voice-transport ok
- ✓ 4-case simulated-log harness:
  1. GoAway → 1011 "service unavailable" unrecovered → ok (was fail)
  2. Unrecovered 1011 quota, no GoAway preceding → fail (as before)
  3. GoAway → 1011 → setup complete (reconnected) → ok
  4. Recovered idle-timeout + fresh 1011 with no new GoAway → fail
     (proves the flag correctly resets on setup complete)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…mpat) (sonichi#262)

* voice-agent: gate googleSearch on VOICE_GOOGLE_SEARCH env var

Isolates the second voice-agent-specific 3.1 compat issue discovered
while landing the Gemini 3.1 stack today. Chi's sonichi#259 (dedup) +
Susan's bodhi sonichi#2 (sendAudio) + my bodhi sonichi#3 (sendFile) all merged,
npm installed, and 2.5 continued to work fine — but flipping .env to
gemini-3.1-flash-live-preview still hit 1011 "exceeded your current
quota" within 200ms of WebSocket setup. Misleading error text; the
actual cause turned out to be the googleSearch + native-audio combo.

Experiment: flipped googleSearch: true → false on an investigation
branch, kept everything else identical (.env to 3.1, same dist,
same tool list). Result: setup complete in 315ms, no 1011. Flipped
back to googleSearch: true + 3.1 and the 1011 returned. Reproducible.

So: Gemini 3.1 native audio rejects the googleSearch grounding
tool entry. 2.5 silently accepted it. The rejection manifests as
the same misleading "quota" close code that caught us on sonichi#259's
duplicate-declaration bug earlier.

Fix: env-var gate. VOICE_GOOGLE_SEARCH defaults to 'true' to
preserve existing 2.5 behavior. Users unpinning VOICE_NATIVE_AUDIO_MODEL
to 3.1 set VOICE_GOOGLE_SEARCH=false in .env at the same time.
Comment at the declaration explains the constraint and points at
today's investigation.

Voice-agent LOSES google-search grounding on 3.1. Google search was
used for "quick factual lookups" per the system instructions; 3.1
users will need to route those queries through the work tool
(sutando-core) instead. Acceptable trade-off.

Test plan:
- ✓ tsc clean
- ✓ Default (VOICE_GOOGLE_SEARCH unset, 2.5 pinned): setup complete,
  googleSearch: true as before
- ✓ Investigation: VOICE_GOOGLE_SEARCH=false + 3.1 pinned → Gemini
  setup complete in 315ms, no 1011
- Live: user needs to test the full 3.1 session (greeting, tool
  call, voice goodbye, reconnect) before we can call 3.1 "ready"

Follow-up: consider auto-inferring googleSearch=false when
VOICE_NATIVE_AUDIO_MODEL contains "3.1" — would remove the need
for users to set two env vars together. Not doing it here because
the explicit config is more discoverable and easier to revert.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* sync package-lock.json to @google/genai 1.49 from sonichi#259

PR sonichi#262 branched before sonichi#259 merged, so the branch's package-lock
still had @google/genai@1.48 while package.json had 1.49 (inherited
from the rebase pull). CI failed with:

  npm error Invalid: lock file's @google/genai@1.48.0 does not
  satisfy @google/genai@1.49.0

Regenerated lockfile via `npm install` (no args). No other changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…onichi#261)

* Add src/verify-gemini-31.sh — pre-rollout gate for Gemini 3.1 stack

Scripts the post-merge verification after the 3-PR Gemini 3.1 compat
stack lands:
  - bodhi fork sonichi#1 (sendClientContent → sendRealtimeInput text path)
  - bodhi fork sonichi#2 (Susan: sendAudio media → audio)
  - bodhi fork sonichi#3 (me: sendFile mimeType branching)
  - sutando sonichi#259 (Chi: deduplicate tool declarations + SDK bump)

Checks the installed bodhi dist for each wire-format fix using
awk-scoped extraction (simpler grep would false-positive on the
OpenAI realtime transport's sendAudio which also uses `audio:` as a
flat field, so the 3.1 gemini-specific check has to be scoped to the
Gemini transport's function body).

Checks sutando src for the phone conversation-server's tool dedup
comment as a PR sonichi#259 marker.

Checks .env for the 2.5 pin (exits green on 2.5, warns on 3.1 — so
the script is safe to run repeatedly before, during, and after the
rollout).

Exits non-zero with "NOT READY for 3.1 rollout" if any wire-format
fix is missing. On success, prints the manual next-steps checklist
for unpinning .env, restarting voice-agent, and running the real
3.1 session test.

Usage:
  bash src/verify-gemini-31.sh              # check current state
  bash src/verify-gemini-31.sh --install    # npm install + check

Tested on the current state: correctly reports PR sonichi#1 applied,
PR sonichi#2 + sonichi#3 + sonichi#259 not yet. Will flip to "ready" once those merge
and an `npm install` pulls the updated bodhi SHA.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* verify-gemini-31: check VOICE_GOOGLE_SEARCH pin in .env

Extends the script to enforce the second 3.1 compat requirement
discovered in sonichi#262: VOICE_GOOGLE_SEARCH must be 'false' when
VOICE_NATIVE_AUDIO_MODEL is pinned to 3.1. Without it, voice-agent
hits a misleading 1011 close on connect.

Check matrix:
  2.5 + default (googleSearch=true)   → pass
  2.5 + VOICE_GOOGLE_SEARCH=false     → warn (unnecessarily losing search)
  3.1 + VOICE_GOOGLE_SEARCH=false     → pass
  3.1 + default (googleSearch=true)   → fail (1011 on connect)
  3.1 + unset VOICE_GOOGLE_SEARCH     → fail (defaults to true → 1011)

Also updates the 'Manual next steps' tail to tell users to set BOTH
env vars together when flipping to 3.1.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

1 participant