Skip to content

fix(twilio): an acknowledgement said before a kept line reaches its question isn't its answer, and indications play whole while the flow keeps the agent uninterrupted - #1493

Merged
yileicn merged 1 commit into
SciSharp:masterfrom
yuyixg:fix/relay-heard-before-answer
Nov 13, 2026

Conversation

@yuyixg

@yuyixg yuyixg commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Why

Two things seen on one dev call:

  1. An acknowledgement taken as the answer to a question not heard yet. The agent's line "Someone will be out within the next three days. I'm sending you a link ... I just texted it to you. Can you check if you got it?" was kept uninterrupted. The caller said "Perfect." to its first sentence; it came in 2.8 s after the line went out, past TooSoonToAnswer (1 s), so it was taken as "yes, I got the link", and the request number went out before the caller heard the question.
  2. Lines said along the way cut off at 0 ms. The recap said while the request went in, and the line before it, were each stopped by an interrupt 0 ms into them with no words after it, so the caller heard neither. The reply after them, kept uninterrupted, played whole.

What

  • For a line that plays to the end, IsBackchannelTail now waits until its question could have been heard: TooSoonToAnswer plus the words before the first question at the fastest speaking pace (20 characters a second, ConversationRelayLines.BeforeFirstQuestion). Only acknowledgements are dropped, as before; anything more than that is never dropped, and a line the caller may cut into is unchanged (Twilio stops it where they speak).
  • An indication said while the flow keeps the agent uninterrupted (ConversationRelayStates.KeepUninterrupted) plays to the end too.

Tests

16 session tests over a fake WebSocket (not in the repo, which has no Twilio test project), run on this branch rebased on master:

  • an acknowledgement said before a kept line reaches its question is dropped; one said once it was reached is taken;
  • a kept line that opens with its question, and a line the caller may cut into, behave as before;
  • words that are more than an acknowledgement are never dropped;
  • an indication plays to the end while the flow keeps the agent uninterrupted, and not otherwise;
  • BeforeFirstQuestion keeps times such as "8 a.m. and 5 p.m.?" in one sentence.

🤖 Generated with Claude Code

…uestion isn't its answer, and indications play whole while the flow keeps the agent uninterrupted

Two things seen on one dev call (4bcd88e1):

- "Perfect.", said to "Someone will be out within the next three days." while the rest of the line ("... I just texted
  it to you. Can you check if you got it?") still played, came in 2.8 s after the line went out and was taken as the
  link checked; the request number went out next, before the caller heard the question. For a line played to the end,
  an acknowledgement is now dropped as a backchannel tail until the question could have been heard: the words before
  it, at the fastest speaking pace (20 characters a second), on top of TooSoonToAnswer. A line the caller may cut into
  is left as it was.
- The recap said while the request went in, and the line before it, were each cut off by an interrupt 0 ms into them
  with no words after it. An indication said while the flow keeps the agent uninterrupted now plays to the end too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@yileicn

yileicn commented Nov 13, 2026

Copy link
Copy Markdown
Member

✅ AegisCode Code Review Report: Pass

Counted: 5 Low. None of them blocks this change.
Full report · review 11501 · commit bf843349

Low · .../BotSharp.Plugin.Twilio/Services/ConversationRelayLines.cs:22 · business-logic: A lowercase initial does not necessarily indicate an abbreviation continuation.

A lowercase initial does not necessarily indicate an abbreviation continuation. For example, in "I just texted you a link. did you get it?", the question is merged into the preceding statement, making the first question-bearing sentence index zero and returning an empty prefix. This contradicts the intended calculation using "the words before the first question" and can remove the additional acknowledgement delay. Normally capitalized questions remain supported.

Suggested fix: [Needs Review - Inconclusive] Restrict continuation merging to recognized abbreviation boundaries, such as internal periods in a.m. and p.m., rather than treating every lowercase-starting piece as a continuation.

Low · .../BotSharp.Plugin.Twilio/Services/ConversationRelaySession.cs:37 · Constant naming: Constant 'FastestSpokenCharsPerSecond' (line 37) is not UPPER_CASE

Constant 'FastestSpokenCharsPerSecond' (line 37) is not UPPER_CASE

Suggested fix: [Validated by static analysis] Rename 'FastestSpokenCharsPerSecond' to 'FASTEST_SPOKEN_CHARS_PER_SECOND'

Low · .../BotSharp.Plugin.Twilio/Services/ConversationRelaySession.cs:91 · style: NoteLine now has four parameters, exceeding the C# standard's three-parameter limit, and introduces…

NoteLine now has four parameters, exceeding the C# standard's three-parameter limit, and introduces another boolean flag. The current implementation is understandable, but the growing combination of timing and playback options makes call sites harder to maintain.

Suggested fix: [Needs Review - Inconclusive] Group the line's timing and playback metadata into a small options or state object so NoteLine stays within the parameter limit without accumulating independent boolean flags.

Low · .../BotSharp.Plugin.Twilio/Services/ConversationRelaySession.cs:385 · Line length: This line is 127 characters long; the maximum is 125

This line is 127 characters long; the maximum is 125

Suggested fix: [Validated by static analysis] Wrap the line so that no line exceeds 125 characters

Low · .../BotSharp.Plugin.Twilio/Services/ConversationRelaySession.cs:484 · Line length: This line is 126 characters long; the maximum is 125

This line is 126 characters long; the maximum is 125

Suggested fix: [Validated by static analysis] Wrap the line so that no line exceeds 125 characters

@yileicn
yileicn requested a lite review from Copilot November 13, 2026 08:42
@yileicn
yileicn merged commit f592a9d into SciSharp:master Nov 13, 2026
4 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Three moderate issues remain: compound questions can still be misclassified, streamed kept lines do not preserve their keep state, and prerecorded indication prompts remain interruptible.

3 open issues
What changed in this PR

This pull request improves Twilio ConversationRelay timing and playback so acknowledgements are not treated as answers before an uninterrupted line reaches its question, and indications can play fully when uninterrupted speech is required.

Changes:

  • Adds pre-question timing based on the text preceding the first question.
  • Tracks whether the latest line must play uninterrupted.
  • Applies uninterrupted playback behavior to spoken indications.
File Description
src/​Plugins/​BotSharp.Plugin.Twilio/​Services/​ConversationRelaySession.cs Updates acknowledgement filtering, uninterrupted line state tracking, and indication playback behavior.
src/​Plugins/​BotSharp.Plugin.Twilio/​Services/​ConversationRelayLines.cs Adds parsing for text occurring before the first question to support playback timing.

Review effort: Lite

Comment on lines +81 to +82
var first = sentences.FindIndex(s => s.EndsWith('?'));
return first <= 0 ? string.Empty : string.Join(" ", sentences.Take(first));
{
if (!_relay.DropBackchannelTails || _lastLineHeardSome || _lastLine == null || !_lastLine.Contains('?')
|| DateTime.UtcNow >= _lastLineSentUtc + TooSoonToAnswer)
|| DateTime.UtcNow >= _lastLineSentUtc + TooSoonToAnswer + UntilQuestionPlays())
Comment on lines +1294 to 1297
var keep = ReadKeepUninterrupted();
foreach (var segment in indication.Split('|'))
{
var text = segment.Trim();
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.

3 participants