Heartbeat: duplicate suppression and activity check

t-536·WorkTask·
·
·
Created3 weeks ago·Updated3 weeks ago

Description

Edit

Improve heartbeat system with duplicate suppression and activity check.

Background

Comparing Ava's heartbeat to moltbot revealed two important gaps:

1. Duplicate Suppression - Moltbot tracks last heartbeat message, suppresses repeats within 24h 2. Activity Check - Moltbot skips heartbeat if user is actively chatting

Without these, Ava might:

  • Nag the user with the same "you have 3 reminders" every 30 minutes
  • Interrupt mid-conversation with a heartbeat

Implementation

1. Duplicate Suppression

Track last heartbeat message in settings or dedicated table:

-- In Memory.hs or Heartbeat.hs
getLastHeartbeatMessage :: IO (Maybe (Text, UTCTime))
setLastHeartbeatMessage :: Text -> UTCTime -> IO ()

-- Could use settings table:
-- key: "heartbeat.last_message", value: "You have 3 reminders"
-- key: "heartbeat.last_sent_at", value: "2024-01-29T22:00:00Z"

In runHeartbeatOnce (Heartbeat.hs), before sending:

case result of
  HeartbeatContent content -> do
    now <- getCurrentTime
    lastMsg <- getLastHeartbeatMessage
    let isDuplicate = case lastMsg of
          Just (prevText, prevTime) ->
            content == prevText && 
            diffUTCTime now prevTime < 86400  -- 24 hours
          Nothing -> False
    unless isDuplicate $ do
      sendMessage content
      setLastHeartbeatMessage content now
  _ -> pure ()

2. Activity Check

Track last user message time, skip heartbeat if too recent:

Option A: Pass last activity time to heartbeat loop

-- In Bot.hs, track last user message
lastActivityVar :: TVar UTCTime

-- Update on each user message
atomically $ writeTVar lastActivityVar now

-- In startHeartbeatLoop, check before running:
startHeartbeatLoop ... lastActivityVar = do
  ...
  lastActivity <- readTVarIO lastActivityVar
  now <- getCurrentTime
  let idleSeconds = diffUTCTime now lastActivity
  when (idleSeconds > 60) $ do  -- Only if idle 1+ minute
    result <- runHeartbeatOnce ...

Option B: Check if currently processing (simpler)

-- If we have a "processing" flag
isProcessing <- readTVarIO processingVar
unless isProcessing $ runHeartbeatOnce ...

3. Config Options (Optional)

Add to HeartbeatConfig:

data HeartbeatConfig = HeartbeatConfig
  { ...
  , heartbeatDuplicateSuppressionHours :: Int  -- default 24
  , heartbeatMinIdleSeconds :: Int             -- default 60
  }

Files to Modify

1. Omni/Ava/Telegram/Heartbeat.hs

  • Add getLastHeartbeatMessage, setLastHeartbeatMessage
  • Add duplicate check in runHeartbeatOnce
  • Optionally add config fields

2. Omni/Ava/Telegram/Bot.hs

  • Add lastActivityVar :: TVar UTCTime
  • Update on user message receipt
  • Pass to startHeartbeatLoop or check before running

3. Omni/Agent/Memory.hs (if using settings)

  • No changes needed, use existing getSetting/setSetting

Testing

1. Unit test: duplicate detection logic 2. Unit test: idle time calculation 3. Manual test: send same reminder situation, verify no repeat within 24h 4. Manual test: chat actively, verify heartbeat doesn't interrupt

Reference

See moltbot implementation:

  • /home/ben/src/moltbot/moltbot/src/infra/heartbeat-runner.ts lines 580-610 (duplicate check)
  • /home/ben/src/moltbot/moltbot/src/infra/heartbeat-runner.ts lines 430-433 (queue check)

Timeline (2)

🔄[human]Open → InProgress3 weeks ago
🔄[human]InProgress → Done3 weeks ago