Skip to content
Learn Agentic AI10 min read0 views

Proactive Agents: AI That Initiates Conversations and Suggests Next Actions

Design proactive conversational AI agents that initiate helpful interactions at the right time, suggest relevant next actions, and respect user preferences around unsolicited outreach.

Beyond Reactive Conversations

Most conversational agents are purely reactive — they wait for the user to say something and respond. Proactive agents flip this dynamic by identifying opportunities to initiate helpful interactions. A shipping agent that notifies you about a delay before you ask, or an onboarding assistant that suggests the next step when you have been idle — these create significantly better user experiences.

The challenge is doing this without being annoying. Proactive agents must balance helpfulness against interruption cost, respect user preferences, and time their outreach for maximum relevance.

Trigger System Design

Every proactive interaction starts with a trigger — an event or condition that warrants reaching out to the user.

from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum
from typing import Callable, Optional


class TriggerType(Enum):
    EVENT = "event"       # Something happened
    TIME = "time"         # Scheduled or deadline-based
    INACTIVITY = "inactivity"  # User has been idle
    THRESHOLD = "threshold"    # A metric crossed a limit


@dataclass
class Trigger:
    name: str
    trigger_type: TriggerType
    condition: Callable[..., bool]
    message_template: str
    relevance_score: float  # 0.0-1.0
    cooldown_minutes: int = 60  # Minimum gap between firings
    last_fired: Optional[datetime] = None

    def can_fire(self) -> bool:
        if self.last_fired is None:
            return True
        elapsed = datetime.now() - self.last_fired
        return elapsed > timedelta(minutes=self.cooldown_minutes)

    def fire(self, context: dict) -> Optional[str]:
        if not self.can_fire():
            return None
        if not self.condition(context):
            return None
        self.last_fired = datetime.now()
        return self.message_template.format(**context)

The cooldown mechanism is essential. Without it, a trigger that remains true (like "user has not completed onboarding") would fire repeatedly.

Relevance Scoring and Priority

When multiple triggers fire simultaneously, the agent needs to pick the most relevant one. Sending three proactive messages at once overwhelms users.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

class ProactiveEngine:
    def __init__(self, max_messages_per_hour: int = 2):
        self.triggers: list[Trigger] = []
        self.max_per_hour = max_messages_per_hour
        self.sent_this_hour: int = 0
        self.hour_start: datetime = datetime.now()
        self.user_preferences = {
            "proactive_enabled": True,
            "quiet_hours_start": 22,  # 10 PM
            "quiet_hours_end": 8,     # 8 AM
        }

    def add_trigger(self, trigger: Trigger):
        self.triggers.append(trigger)

    def check_quiet_hours(self) -> bool:
        hour = datetime.now().hour
        start = self.user_preferences["quiet_hours_start"]
        end = self.user_preferences["quiet_hours_end"]
        if start > end:  # Spans midnight
            return hour >= start or hour < end
        return start <= hour < end

    def evaluate(self, context: dict) -> Optional[str]:
        if not self.user_preferences["proactive_enabled"]:
            return None

        if self.check_quiet_hours():
            return None

        # Reset hourly counter
        if datetime.now() - self.hour_start > timedelta(hours=1):
            self.sent_this_hour = 0
            self.hour_start = datetime.now()

        if self.sent_this_hour >= self.max_per_hour:
            return None

        # Collect and rank eligible triggers
        candidates = []
        for trigger in self.triggers:
            message = trigger.fire(context)
            if message:
                candidates.append((trigger.relevance_score, message))

        if not candidates:
            return None

        candidates.sort(key=lambda x: x[0], reverse=True)
        self.sent_this_hour += 1
        return candidates[0][1]

Defining Practical Triggers

engine = ProactiveEngine(max_messages_per_hour=2)

engine.add_trigger(Trigger(
    name="onboarding_incomplete",
    trigger_type=TriggerType.INACTIVITY,
    condition=lambda ctx: (
        not ctx.get("onboarding_complete")
        and ctx.get("idle_minutes", 0) > 10
    ),
    message_template=(
        "I noticed you haven't finished setting up your profile. "
        "Would you like help completing step {next_step}?"
    ),
    relevance_score=0.7,
    cooldown_minutes=120,
))

engine.add_trigger(Trigger(
    name="shipping_delay",
    trigger_type=TriggerType.EVENT,
    condition=lambda ctx: ctx.get("shipping_delayed", False),
    message_template=(
        "Heads up: your order {order_id} has a shipping delay. "
        "New estimated delivery is {new_eta}. "
        "Would you like to see options?"
    ),
    relevance_score=0.95,
    cooldown_minutes=30,
))

# Evaluate with current context
context = {
    "onboarding_complete": False,
    "idle_minutes": 15,
    "next_step": 3,
    "shipping_delayed": True,
    "order_id": "ORD-4821",
    "new_eta": "March 20",
}

message = engine.evaluate(context)
print(message)  # Shipping delay wins (higher relevance)

Respecting User Preferences

Proactive agents must provide opt-out controls. Store user preferences for notification types, frequency limits, and quiet hours. Always honor "do not disturb" signals immediately.

def update_preferences(engine: ProactiveEngine, user_input: str):
    lower = user_input.lower()
    if "stop" in lower or "no more" in lower:
        engine.user_preferences["proactive_enabled"] = False
        return "Proactive notifications disabled. You can re-enable anytime."
    if "quiet" in lower:
        engine.user_preferences["quiet_hours_start"] = 20
        engine.user_preferences["quiet_hours_end"] = 9
        return "Quiet hours set from 8 PM to 9 AM."
    return None

FAQ

How do you prevent proactive agents from feeling spammy?

Three mechanisms work together: cooldown periods between trigger firings, hourly message caps, and relevance thresholds that filter out low-value notifications. Additionally, track user engagement with proactive messages — if a user dismisses three in a row, automatically reduce frequency or pause until they initiate a conversation.

What triggers justify unsolicited outreach?

High-urgency, time-sensitive events are the best candidates: security alerts, delivery issues, approaching deadlines, or service disruptions. Low-urgency suggestions like "did you know about this feature?" should be rate-limited aggressively and tied to specific user activity patterns that suggest genuine need.

How do you measure the success of proactive interactions?

Track the engagement rate (what percentage of proactive messages get a user response), the resolution rate (did the proactive message lead to a completed action), and the opt-out rate. A healthy proactive system has engagement above 30 percent and opt-out below 5 percent per month.


#ProactiveAI #AgentDesign #TriggerSystems #ConversationalAI #Python #AgenticAI #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.