Skip to content

Live suggestions fire on interviewer backchannel, not just real questions #100

Description

@alpha5611331

Problem

Every ASR final on the interviewer channel (ch_0) fires a full live-suggestion request, regardless of whether the turn was a real question or pure backchannel ("mhm", "yeah", "okay", "got it"). The only mechanism for "no answer needed" is the backend emitting a NO_SUGGESTION_NEEDED sentinel inside the answer stream itself, after the client has already:

  • uploaded the full profile + context + up to 60 transcripts
  • started a model generation
  • rendered a Pending card that then has to be retracted

Three concrete failure modes:

  1. Cost/latency paid before the decision. The cheapest, most certain signal ("this is filler") runs last, not first.
  2. In-band control signal. The decision travels inside the prose stream it's supposed to gate. It already broke once - professional mode's bold headline made the model emit **NO_SUGGESTION_NEEDED**, requiring a markdown-stripping regex in suggestion-sentinel.ts to keep matching.
  3. Wrong trigger boundary. An ASR final is an acoustic endpoint, not a finished thought - a question split by a thinking pause ("So tell me about..." / "...your Kafka work") fires two requests, and the second aborts the first mid-render.
  4. Show-then-retract UI. The panel renders a spinner immediately and pulls it back on every "okay, got it", which reads as a bug even when working as designed.

Fix

A cascade, cheapest and most certain signal first:

  • Stage 0/1 (client, deterministic) - classifyInterviewerTurn() runs in-process on the merged interviewer turn and returns Skip / Answer / Uncertain. Skip drops the turn with no request and no card. Answer (a completed question or directive) generates immediately. Uncertain parks on a short settle timer that a continuation re-arms, so a split question is judged whole.
  • Stage 2 (backend, speculative) - for Uncertain turns, a cheap one-word classifier (no profile, no context, free-tier model) runs beside the real generation as a background task rather than in front of it, so a real question pays zero added latency. A SKIP verdict cancels the generation and yields the sentinel; every gate failure falls open to answering.
  • The client no longer renders a Pending card on request start - it holds briefly so a turn the backend gate suppresses produces no card at all instead of one that flashes and disappears.

The NO_SUGGESTION_NEEDED sentinel remains as the last-resort fallback for turns neither stage can settle.

PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions