Skip to content

Absence awareness: detect user gaps and generate re-entry briefings #12

Description

@tyejcoleman

Problem

The heartbeat system treats every tick the same regardless of how long the user has been away. After a 3-day absence, the first tick runs the normal pipeline: confluence scoring, cooldown checks, fingerprint dedup. This means the user might get a single low-priority nudge instead of a comprehensive "here's what happened while you were gone" briefing.

The system already tracks session_messages (table in sqlite_migrate.go) with timestamps, so detecting absence is straightforward.

Proposal

Detection

Before the normal signal evaluation in evaluateShouldAct(), check for absence:

func (k *Keyoku) detectAbsence(ctx context.Context, entityID string) (bool, time.Duration) {
    // Query session_messages for most recent user message
    // SELECT MAX(created_at) FROM session_messages 
    // WHERE entity_id = ? AND role = 'user'
    lastActivity := ...
    gap := time.Since(lastActivity)
    return gap > 24*time.Hour, gap
}

Re-entry mode

When absence > 24h, bypass the normal decision pipeline and enter re-entry mode:

  1. Skip confluence threshold, cooldown, fingerprint dedup, and fatigue checks
  2. Collect ALL active signals (run all 14 HeartbeatCheckType checks)
  3. Sort by tier priority (tierPriority map: Immediate=0, Elevated=1, Normal=2, Low=3)
  4. Include positive deltas (CheckPositiveDeltas) to balance bad news
  5. Set DecisionReason = "reentry" on the HeartbeatResult
  6. Set ShouldAct = true with EscalationLevel = 0 (fresh start, not nagging)
  7. If LLM analysis is enabled (cfg.llmProvider != nil), call LLM once with all signals to generate a prioritized briefing summary
  8. Include absence duration in result: "You've been away for 3 days"

Format hint

Add a field to HeartbeatResult:

AbsenceDuration *time.Duration  // nil if not a re-entry tick

The plugin's formatHeartbeatContext() in context.ts can use this to switch to a briefing format.

Files to modify

  • heartbeat_decide.go — add detectAbsence(), add re-entry branch at top of evaluateShouldAct() (before first-contact check)
  • heartbeat.go — add AbsenceDuration field to HeartbeatResult
  • storage/sqlite_heartbeat.go — add GetLastUserActivity() query against session_messages

Plugin side (separate PR in keyoku repo)

  • packages/openclaw/src/context.ts — add re-entry formatting path in formatHeartbeatContext()
  • packages/types/src/memory.ts — add absence_duration_ms?: number to HeartbeatContextResult

Constraints

  • Only triggers once per absence (after re-entry tick, normal pipeline resumes)
  • Absence threshold: 24h (configurable via HeartbeatParams)
  • Re-entry tick should NOT reset fatigue counters (user hasn't acted yet)
  • If no signals at all after absence, send a simple "Welcome back, nothing happened" instead of silence

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions