33Engagement Scheduler (spec §9, §5, §6, §7)
44
55Change log (2025-08-19):
6- - If the **top-ranked** candidate is suppressed due to **debounce**, do NOT fall
7- back to the next candidate — return None for this cycle to avoid “nagging”.
8- - Still allow fallback when the top candidate is suppressed due to **cooldown**
9- (social cooldown) or other non-debounce reasons. This preserves behavior for
10- tests that expect object interaction when shoulder is blocked by policy/safety.
6+ - Evaluate **social cooldown before debounce** for human-touch candidates.
7+ If cooldown blocks the top candidate, we DO fall back to the next candidate.
8+ - Preserve "no fallback on pure debounce for the top candidate" to avoid nagging.
119"""
1210
1311from __future__ import annotations
@@ -163,12 +161,10 @@ def decide(
163161 return nudge
164162
165163 # If the **top** candidate was blocked due to **debounce**, do not fall back.
166- # Return None to avoid appearing as “nagging” by switching targets immediately.
167164 if idx == 0 and reason == "debounce" :
168165 return None
169166
170167 # For cooldown or other reasons, try the next candidate.
171- # (E.g., social cooldown should still allow an object-interaction nudge.)
172168 continue
173169
174170 return None
@@ -242,8 +238,8 @@ def _candidate_to_nudge_with_reason(
242238 ) -> Tuple [Optional [Nudge ], str ]:
243239 """
244240 Convert a ranked candidate into a Nudge while enforcing:
245- - social cooldown (no repeated touches too quickly );
246- - debouncing of identical nudges within window;
241+ - ** social cooldown** (checked first for human targets );
242+ - ** debouncing** of identical nudges within window;
247243 - consent gate for social touch;
248244 - assign GREEN vs. YELLOW nudge level per spec logic.
249245
@@ -255,14 +251,14 @@ def _candidate_to_nudge_with_reason(
255251 category = str (a .get ("category" , "object" ))
256252 risk_level : SafetyLevel = a ["_risk_level" ]
257253
258- # Debounce identical nudges:
259- if self .cooldowns .debounce (name , xyz , self .policy .debounce_window_s ):
260- return None , "debounce"
261-
262- # Social cooldown: only for human-target nudges (e.g., shoulder).
254+ # --- Social cooldown FIRST for human targets ---
263255 if category == "human" and self .cooldowns .social_cooldown_active (self .policy .social_cooldown_s ):
264256 return None , "cooldown"
265257
258+ # --- Debounce (applies to any category) ---
259+ if self .cooldowns .debounce (name , xyz , self .policy .debounce_window_s ):
260+ return None , "debounce"
261+
266262 # Determine nudge level:
267263 if category == "human" and name == "shoulder" :
268264 # Consent handling for social touch.
0 commit comments