> ## Documentation Index
> Fetch the complete documentation index at: https://developers.autoplay.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Step 3 — Define proactive triggers

> Decide when to surface proactive help, then deliver it as chat messages with optional reply options (SDK) or as on-screen visual guidance (BYO) — with registry and FSM gating.

<Frame caption="Two deliveries after the same proactive trigger: Flow A — textual proactive trigger, user chats, then visual guidance; Flow B — visual guidance directly (no chat step).">
  <img src="https://mintcdn.com/autoplayai/rNVIPereGwWPaZqE/images/sdk/proactive-copilot/step-3-define-proactive-triggers/proactive-trigger-two-flows.svg?fit=max&auto=format&n=rNVIPereGwWPaZqE&q=85&s=d1127dc06686dc03d24ea22456237cb4" alt="Diagram: proactive trigger splits into Flow A (textual proactive trigger, user interacts with support AI agent, then visual guidance) and Flow B (visual guidance only, no support AI agent step)." className="doc-embed" width="680" height="420" data-path="images/sdk/proactive-copilot/step-3-define-proactive-triggers/proactive-trigger-two-flows.svg" />
</Frame>

<Info>Available now.</Info>

<Warning>
  **Prerequisite for this step:** set up
  [`autoplay_sdk.agent_state_v2.SessionState`](../agent-states) first. Proactive
  triggers should be evaluated from `thinking` and delivered only when your v2
  state + cooldown logic allows it.
</Warning>

Proactive help is **not** “run the LLM on every event.” In this step, focus on **detection + state gating** for proactive chat offers with optional reply options. Use these same trigger outputs in [Step 4](./step-4-enhance-with-user-memory) when you want to route into relevant visual guidance.

### When to fire (shared layer)

1. **Context** — On each tick, build `ProactiveTriggerContext` from your event buffer, session metadata, and any flags you maintain (URLs, workflow hints, etc.).
2. **Triggers** — Implement `ProactiveTrigger` (predicate-style or custom) that returns a `ProactiveTriggerResult` (`body`, optional `reply_option_labels`, timings, cooldown metadata) or `None`.
3. **Registry** — Wrap triggers in `ProactiveTriggerRegistry` and call **`evaluate_first`** (or `evaluate_all`) to pick the first firing trigger.
4. **Gate** — Before sending, use [Agent session states](../agent-states): apply v2 state checks + cooldown gating so you keep **one proactive offer at a time** and avoid overlap while reactive chat is active.

### Why agent states are required (v2)

Treat proactive triggers as **state-gated orchestration**, not just predicates:

* **`thinking`** — only state where a new proactive offer may be initiated.
* **`proactive_assistance`** — a proactive offer is already shown; do not stack another proactive suggestion.
* **`reactive_assistance`** — user-initiated chat is active; suppress overlapping unsolicited proactive prompts.

This separation is what prevents intrusive behavior and overlapping proactive suggestions.

### Trigger conditions and heuristics

Not every event should fire help — that becomes **noise**. Encode **intent and workflow** conditions using your payload and memory (when available) — not ad-hoc URL rules alone — before you evaluate triggers and deliver proactive chat.

**Examples of situations worth detecting:**

* Inferred intent is stuck on the same **workflow step** (no forward progress toward the session goal) for more than 60 seconds
* The user hits **failure signals** for the same **workflow** three times in a row (for example validation or integration errors within one adoption flow)
* The user **starts a feature workflow** they have **never completed** before (first-time path through that workflow, not “first visit” to a URL)
* **Workflow completion** for a key journey stays low across attempts — for example stuck in `in_progress` with completion below a threshold while memory shows repeat struggle on that workflow

Use heuristics like:

| Signal                                        | Trigger condition                                                                 | Example suggestion                                                                              |
| --------------------------------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Workflow stall (no progress on inferred goal) | Same workflow step / intent with no forward motion for several actions or minutes | "It looks like you're circling this step — want a nudge on what comes next?"                    |
| User skips a step in the golden path          | Off the ideal path                                                                | "Most users connect a data source before creating a dashboard."                                 |
| User returns after a gap of 3+ days           | Resuming an incomplete task                                                       | "Last time you were setting up your first integration — want to pick up where you left off?"    |
| User has never completed a key workflow       | Adoption gap                                                                      | "You haven't finished exporting a report yet — here's the short path."                          |
| User is in `in_progress` on a workflow        | Repeat attempt without crossing completion                                        | "You got 85% through this last time — the one remaining step is verifying the success message." |

Put these checks **before** your registry evaluation and delivery. In this step, focus on proactive trigger definition plus state-gated **chat / reply-option** delivery. Step 4 covers routing into relevant visual guidance tours.

### Choose your trigger path: built-in vs custom

You can implement Step 3 in two ways:

## Use built-in proactive triggers

Use this path when default patterns are enough and you want quick setup with `default_proactive_trigger_registry()`.

1. Start with the built-in registry using `default_proactive_trigger_registry()`.
2. Evaluate with `evaluate_first(ctx)` on each tick.
3. State-gate delivery with `can_show_proactive_with_reason` before showing UI.

For built-in IDs, default registry behavior, and tuning options, see [Proactive triggers](../proactive-triggers).

## Build a custom proactive trigger

Use this path when you need product-specific workflow logic, custom copy, or domain-specific metadata.

1. **Define** a `PredicateProactiveTrigger` (or subclass `ProactiveTrigger` for more complex logic).
2. **Register** it in `ProactiveTriggerRegistry` (optionally prepending it before built-ins).
3. **Evaluate** with `evaluate_first(ctx)` on each tick.
4. **State-gate delivery** with `can_show_proactive_with_reason` before showing UI.

```python theme={null}
from autoplay_sdk.proactive_triggers import (
    PredicateProactiveTrigger,
    ProactiveTriggerRegistry,
    default_proactive_trigger_registry,
)

custom_trigger = PredicateProactiveTrigger(
    trigger_id="stalled_on_checkout",
    body="Looks like this step is blocking progress — want a quick walkthrough?",
    predicate=lambda ctx: any("/checkout" in (u or "") for u in ctx.canonical_urls[-3:]),
    reply_option_labels=["Yes, show me", "Not now"],
)

registry = ProactiveTriggerRegistry([
    custom_trigger,
    *default_proactive_trigger_registry().triggers,
])
```

For the full end-to-end authoring flow (context shaping, timings, delivery hooks), see [Authoring proactive triggers](../proactive-triggers-authoring).

### Delivery A: Proactive chat and reply options (SDK)

The SDK is **transport-agnostic** for *detection*; you wire a firing `ProactiveTriggerResult` into your **delivery** layer (chat widget message, in-app chat, toast, etc.). That is the path fully covered by the reference pages.

| Topic                                                                                           | Page                                                                |
| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Types, built-in IDs, connector JSON, **`user_page_dwell` defaults** (1 minute + sparse actions) | **[Proactive triggers](../proactive-triggers)**                     |
| End-to-end authoring walkthrough                                                                | **[Authoring proactive triggers](../proactive-triggers-authoring)** |
| Agent State v2 (`SessionState`), FSM gating, idle expiry, snapshots                             | **[Agent session states](../agent-states)**                         |

For v2-first integrations, keep the same flow (`context -> registry -> delivery`) but enforce that proactive delivery happens only from `thinking`, with cooldown-aware state transitions.

```python theme={null}
from autoplay_sdk.proactive_triggers import (
    ProactiveTriggerContext,
    ProactiveTriggerRegistry,
    default_proactive_trigger_registry,
)
from autoplay_sdk.agent_states import can_show_proactive_with_reason

registry = default_proactive_trigger_registry()  # or your own list

async def on_tick(ctx: ProactiveTriggerContext, fsm_state) -> None:
    allowed, reason = can_show_proactive_with_reason(fsm_state)
    if not allowed:
        return
    result = registry.evaluate_first(ctx)
    if result is None:
        return
    # Deliver result.body (+ optional labels) to your UI / webhook; honor cooldowns externally.
```

Start with **[Authoring proactive triggers](../proactive-triggers-authoring)** if you are building your first trigger from scratch.

**Next:** [Step 4 — Connect relevant visual guidance](./step-4-enhance-with-user-memory)
