Heartbeat

Edward's proactive monitoring system — listening for messages, calendar events, and emails without being asked.

Overview

The heartbeat system lets Edward act on incoming information before you open the chat. It monitors iMessage, Apple Calendar, and Apple Mail, triaging each event through a multi-layer classification pipeline to decide whether to ignore, remember, respond, or alert you.

Architecture

┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│  iMessage   │  │  Calendar   │  │    Email    │
│  Listener   │  │  Listener   │  │  Listener   │
└──────┬──────┘  └──────┬──────┘  └──────┬──────┘
       │                │                │
       └────────────────┼────────────────┘
                        ↓
              ┌───────────────────┐
              │   Triage Engine   │
              │  L1 → L2 → L3    │
              └────────┬──────────┘
                       ↓
        ┌──────────────┼──────────────┐
        ↓              ↓              ↓
   [DISMISS]      [NOTE/ACT]    [ESCALATE]
                  memory/reply   reply + push

Listeners

iMessage

Polls the macOS Messages database (~/Library/Messages/chat.db) every 10 seconds for new incoming messages. Uses Apple's timestamp format (nanoseconds since 2001-01-01). Only picks up messages from other people — Edward's own outbound messages are ignored.

Calendar

Polls Apple Calendar via MCP tools at a configurable interval (default 300 seconds). Looks ahead a configurable number of minutes for upcoming events. Useful for proactive reminders about meetings or appointments.

Email

Polls Apple Mail for unread emails. Filters for messages that mention @edward to avoid noise. Only processes emails that explicitly invoke Edward.

3-Layer Triage

Every incoming event passes through up to three triage layers, stopping as soon as a definitive action is determined:

Layer 1 — Rules (Zero Cost)

Fast pattern matching with no LLM calls. Catches obvious cases like known spam senders, automated notifications, or messages from Edward himself. Events that pass Layer 1 move to Layer 2.

Layer 2 — Haiku Classification

Claude Haiku classifies the event's urgency and determines what action to take. This is the primary decision point for most messages. Classification outputs one of the action types below.

Layer 3 — Execute

Carries out the determined action — storing a memory, sending a response via chat_with_memory(), or pushing a notification.

Actions

ActionDescription
DISMISSIgnore the event — no further processing
NOTEStore the information as a memory for future reference
ACTRespond to the message using Edward's full tool access
ESCALATERespond and send a push notification to alert you

@edward Mentions

Messages containing @edward bypass the normal triage flow entirely and are routed directly to Layer 3 for immediate response. This lets anyone in a group chat summon Edward by name.

Listening Windows

When Edward responds to a message, a 5-minute follow-up window opens for that conversation. Any replies during this window are treated as continuations rather than new events, keeping the conversation coherent.

Active Chat Gating

When you're actively chatting with Edward in the web UI, the triage system pauses to avoid duplicate responses. It resumes automatically when the chat goes idle.

Briefing Injection

Pending heartbeat events that require attention are injected into Edward's system prompt. When you open the chat, Edward can proactively brief you on things that happened while you were away.

Configuration

Heartbeat settings are managed via PATCH /api/heartbeat/config or the settings UI:

FieldDefaultDescription
enabledtrueMaster switch for the heartbeat system
poll_seconds10iMessage polling interval
calendar_enabledtrueEnable calendar listener
calendar_poll_seconds300Calendar polling interval
calendar_lookahead_minutes30How far ahead to look for events
email_enabledtrueEnable email listener
email_poll_seconds300Email polling interval