Skip to content

Conversation

@chenghao-mou
Copy link
Member

@chenghao-mou chenghao-mou commented Feb 5, 2026

Adjust the endpointing delays based on the pause statistics of a running session:

EMA for both

  • between utterance pauses (min_delay) and
  • between turn pauses (max_delay)

with values capped to the original min and max.


Open with Devin

@chenghao-mou chenghao-mou marked this pull request as ready for review February 6, 2026 16:29
@chenghao-mou chenghao-mou requested a review from a team February 6, 2026 16:29
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

from ..utils import is_given


class ExponentialMovingAverage:
Copy link
Member

Choose a reason for hiding this comment

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

We have one here:

@chenghao-mou chenghao-mou force-pushed the feat/AGT-2485-dynamic-endpointing-delay branch from fa7cffb to ff0c7b1 Compare February 9, 2026 12:06
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment on lines +212 to +218
if is_given(min_delay):
self._min_delay = min_delay
self._utterance_pause.reset(initial=self._min_delay, min_val=self._min_delay)

if is_given(max_delay):
self._max_delay = max_delay
self._turn_pause.reset(initial=self._max_delay, max_val=self._max_delay)
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 update_options leaves cross-filter clamp bounds stale

When update_options changes min_delay or max_delay, it only updates the bounds on one of the two ExpFilter instances, leaving the other filter's bounds stale. Both _utterance_pause and _turn_pause are initialized with min_val=min_delay, max_val=max_delay, but update_options only resets one filter per parameter.

Root Cause and Impact

At initialization (endpointing.py:39-44), both filters share the same bounds:

self._utterance_pause = ExpFilter(alpha=alpha, initial=min_delay, min_val=min_delay, max_val=max_delay)
self._turn_pause = ExpFilter(alpha=alpha, initial=max_delay, min_val=min_delay, max_val=max_delay)

But update_options only partially updates:

  • update_options(max_delay=2.0) resets _turn_pause with max_val=2.0, but _utterance_pause._max_val stays at the old max_delay (e.g. 1.0). This means min_delay (read from _utterance_pause.value) can never exceed the old max_delay, even though the configured max_delay has increased.
  • update_options(min_delay=0.5) resets _utterance_pause with min_val=0.5, but _turn_pause._min_val stays at the old min_delay (e.g. 0.3). This means max_delay (read from _turn_pause.value) could drop below the new min_delay, violating the invariant max_delay >= min_delay.

Impact: After dynamically updating endpointing delay bounds, the EMA-filtered values can be clamped to incorrect/stale limits, potentially making the dynamic adjustment ineffective or violating the min ≤ max invariant.

Suggested change
if is_given(min_delay):
self._min_delay = min_delay
self._utterance_pause.reset(initial=self._min_delay, min_val=self._min_delay)
if is_given(max_delay):
self._max_delay = max_delay
self._turn_pause.reset(initial=self._max_delay, max_val=self._max_delay)
if is_given(min_delay):
self._min_delay = min_delay
self._utterance_pause.reset(initial=self._min_delay, min_val=self._min_delay)
self._turn_pause.reset(min_val=self._min_delay)
if is_given(max_delay):
self._max_delay = max_delay
self._turn_pause.reset(initial=self._max_delay, max_val=self._max_delay)
self._utterance_pause.reset(max_val=self._max_delay)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

if is_given(alpha):
self._alpha = alpha
self._filtered = -1.0
self._filtered = initial
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 ExpFilter.reset unconditionally clears filtered value even when only updating bounds

The reset method always executes self._filtered = initial (line 30), even when initial is not provided (NOT_GIVEN). This means calling reset(min_val=0.5) to only update the min bound will also discard the accumulated EMA state.

Root Cause and Impact

At exp_filter.py:30, self._filtered = initial runs unconditionally. Since initial defaults to NOT_GIVEN, any call to reset() that doesn't explicitly pass initial will wipe the filtered value:

def reset(self, alpha=NOT_GIVEN, initial=NOT_GIVEN, min_val=NOT_GIVEN, max_val=NOT_GIVEN):
    if is_given(alpha):
        self._alpha = alpha
    self._filtered = initial  # always runs — clears EMA state if initial not provided

This is particularly relevant because the suggested fix for BUG-0001 would call self._turn_pause.reset(min_val=self._min_delay) (without initial), which would unintentionally clear the _turn_pause EMA state. Without fixing this, any caller that wants to update only min_val or max_val via reset() will lose the accumulated filter state.

Impact: Accumulated EMA state is lost when reset is called to update only bounds, causing the filter to restart from scratch.

Suggested change
self._filtered = initial
if is_given(initial):
self._filtered = initial
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

2 participants