New to this? Start with Authoring proactive triggers for a step-by-step walkthrough of building your first trigger.
How it works in three steps
- Build context β On each tick, construct a
ProactiveTriggerContextfrom your event buffer (recent actions, canonical URLs, session summary, etc.). - Evaluate β Call
registry.evaluate_first(ctx). The registry walks its triggers in priority order and returns the firstProactiveTriggerResultwhose predicate matches, orNone. - Deliver β Pass the resultβs
bodyand optionalreply_option_labelsto your delivery layer. Gate onSessionState(v2) before sending.
Two ways to act on a trigger
- Visual guidance (available now) β surface a chat message, quick reply, modal, or in-app tour via your delivery layer (Intercom, a custom UI, etc.)
- Browser agent triggering (coming soon) β fire an autonomous browser agent that completes the task on the userβs behalf β no manual steps required.
Quickstart
Module: autoplay_sdk.proactive_triggers
Core types
| Class / function | Purpose |
|---|---|
ProactiveTriggerContext | Snapshot of session state passed to every trigger on each tick |
ProactiveTriggerRegistry | Ordered list of triggers; call evaluate_first(ctx) to get the first match |
ProactiveTriggerResult | What a firing trigger returns: trigger_id, body, optional reply_option_labels, timings |
PredicateProactiveTrigger | Fast-path trigger for a single boolean condition β no subclassing needed |
ProactiveTriggerEntity | Wraps any trigger to attach explicit ProactiveTriggerTimings |
ProactiveTriggerTimings | Holds interaction_timeout_s and cooldown_s |
Proactive data structures (full map)
| Structure | Layer | Purpose | Typical source |
|---|---|---|---|
ProactiveTriggerConfig | proactive config | One trigger definition (id, name, criteria, messages) | integration_config.proactive_intercom[*] |
TriggerMessage | proactive config | Quick-reply/chip option shown to the user | ProactiveTriggerConfig.messages |
CriteriaLeaf / CriteriaGroup (ProactiveCriteria) | proactive config | Recursive rule tree (AND/OR groups) | proactive_criteria JSON |
ProactiveTriggerContext | proactive runtime | Evaluation input snapshot for one tick | Built from recent actions/summaries |
ProactiveTriggerResult | proactive runtime | Firing result (body, labels, timings, metadata) | Returned by trigger evaluation |
ProactiveTriggerTimings | proactive policy | Idle timeout + refire cooldown policy | Trigger defaults or overrides |
ProactiveTriggerEntity | proactive runtime | Decorator to enforce explicit timings on a trigger | Registry construction |
TourDefinition | tour registry | One visual-guidance tour + timing overrides | integration_config.tour_registry[*] |
TourRegistry | tour registry | Product-level lookup for TourDefinition | Parsed once per product config |
SessionState | agent state v2 | Primary routing and gating state keyed by session_id | Session-state persistence layer |
Default timing constants
These live inautoplay_sdk.proactive_triggers.types and apply whenever you donβt override them:
| Constant | Default | What it controls |
|---|---|---|
DEFAULT_INTERACTION_TIMEOUT_S | 10 s | How long the proactive UI stays open waiting for the user |
DEFAULT_COOLDOWN_S | 30 s | Minimum gap before the same trigger may fire again |
DEFAULT_PROACTIVE_CONTEXT_LOOKBACK_S | 120 s | Action window used by from_actions_payloads factory |
DEFAULT_PROACTIVE_CONTEXT_MAX_ACTIONS | 50 | Max actions loaded by from_actions_payloads factory |
Custom payload sources (RecentActionsPayloadSource)
Implement RecentActionsPayloadSource to load ActionsPayload batches from any backing store (database, Redis, etc.). Call build_proactive_context_from_payloads(...) with the same keyword arguments you would pass to ProactiveTriggerContext.from_actions_payloads β it is a thin wrapper for a single import site.
Monitor cadence and context source
Poll cadence (~10 s proactive monitor default in the stock connector) is separate from the ~120 s action lookback window used to buildProactiveTriggerContext.
Your host/connector can source recent action history from any durable memory backend or an in-process store. Regardless of source, keep the same contract:
- build
ProactiveTriggerContextfrom a recent, ordered action slice, - run proactive delivery evaluation only when
SessionStateis eligible for unsolicited help (effectivelyTHINKING), - continue running idle-expiry checks for already-open proactive UI on each monitor sweep.
Built-in trigger IDs
Stabletrigger_id strings are exported from autoplay_sdk.proactive_triggers.defaults:
| Constant | Value | Trigger class |
|---|---|---|
TRIGGER_ID_CANONICAL_URL_PING_PONG | "canonical_url_ping_pong" | CanonicalPingPongTrigger |
TRIGGER_ID_USER_PAGE_DWELL | "user_page_dwell" | UserPageDwellTrigger |
TRIGGER_ID_SECTION_PLAYBOOK_MATCH | "section_playbook_match" | SectionPlaybookTrigger |
default_proactive_trigger_registry() returns a registry pre-loaded with CanonicalPingPongTrigger only. To use other built-ins, pass an explicit builtins list (see Event connector JSON below). Use this as a starting point and append your own triggers.
get_proactive_trigger_ids() and list_builtin_trigger_catalog() enumerate all available built-in IDs and their catalog metadata at runtime.
user_page_dwell β time on page + sparse actions
The user_page_dwell built-in looks at the trailing run of recent_actions that share the same non-empty canonical_url as the latest action (the βcurrent pageβ streak). It fires only when all of the following hold:
- Dwell time β The time span of that streak is at least
dwell_threshold_seconds.- Default: 60 (one minute). This is the minimum time the user must stay on the same canonical URL before the trigger can match.
- Sparse actions β The number of actions in that streak is at most
user_page_dwell_max_actions.- Default: 5. If the user has more than this many actions on the same URL streak, the trigger does not fire (treats it as active exploration, not passive linger).
ProactiveTriggerContext.context_extra (or, with the stock event connector, under integration_config.proactive_triggers β see below):
| Key | Default | Description |
|---|---|---|
dwell_threshold_seconds | 60 | Minimum seconds on the same canonical URL streak (float; must be > 0). |
user_page_dwell_max_actions | 5 | Maximum actions allowed on that streak for the trigger to fire (int; invalid or β€ 0 falls back to 5). |
dwell_proactive_body | (built-in short message) | Optional override for the proactive body text when the trigger fires (still capped at PROACTIVE_BODY_MAX_CHARS). |
eval_now (Unix time as float) β fixes βnowβ when unit-testing dwell duration.
Fired results include metadata such as user_page_dwell_seconds, user_page_dwell_action_count, and user_page_dwell_max_actions for analytics and debugging.
Section playbook β product_section_playbook + section_playbook
The section_playbook_match built-in selects guidance from section_playbook (section id β message body or structured row) using section intelligence gathered into product_section_playbook:
integration_config.proactive_triggers.section_url_rulesβ ordered array of{ "prefix": "<canonical_url_prefix>", "section_id": "<stable_id>" }. First matching prefix wins (put longer/more specific prefixes first).section_url_fallback_idβ optional bucket id for URLs that match no prefix (defaults internally tootherwhen resolving unmapped paths).
section_url_rules is non-empty, build_proactive_trigger_context_for_session attaches product_section_playbook to context_extra: runtime.current_section_id (latest actionβs resolved section) and sections[section_id] with visit_count, dwell_seconds_per_visit (one float per visit), first_visited_at, last_visited_at (ISO 8601 UTC).
Resolution order for which playbook row fires: non-empty current_section_id if it exists in section_playbook (host override); else highest total dwell among sections that appear in both product_section_playbook.sections and section_playbook (tie-break visit_count); else runtime.current_section_id if it has a playbook row.
The LLM judge receives both product_section_playbook and section_playbook inside context_extra_json when present.
ProactiveTriggerContext fields
| Field | Type | Required | Description |
|---|---|---|---|
session_id | str | β | Identifies the user session |
product_id | str | β | Identifies the product / tenant |
conversation_id | str | None | β | Chat thread ID (aligns cooldown keys with the chat surface) |
action_count | int | β | Total actions seen this session |
canonical_urls | list[str | None] | β | Chronological page URLs β used by CanonicalPingPongTrigger |
recent_actions | tuple[SlimAction, ...] | β | Slice of recent actions from your buffer |
latest_summary_text | str | None | β | Latest session summary from your summarizer |
prior_session_summaries | tuple[str, ...] | β | Past session summaries (from Redis, DB, etc.) |
context_extra | dict | β | Connector-specific data (e.g. dwell_threshold_seconds, user_page_dwell_max_actions, section_playbook, product_section_playbook, section_url_rules, current_section_id) |
session_id and product_id are validated at construction under ScopePolicy.STRICT (default) β both must be non-empty strings. Use ScopePolicy.LENIENT only at legacy call sites.
Event connector JSON (builtins)
If youβre configuring triggers via integration_config.proactive_triggers.builtins (the JSON path used by the stock event connector), each row must include:
idmust match an entry in the built-in catalog (seelist_builtin_trigger_catalog()).nameanddescriptionare required non-empty strings β used for display, analytics, and admin UIs.interaction_timeout_sandcooldown_sare optional; omit to use the catalogβs defaults.registrymust be omitted or"default".mode: "ping_pong_only"restricts results tocanonical_url_ping_pongonly.- Invalid rows raise
SdkConfigErrorand logevent=proactive_builtin_spec_invalid.
integration_config.proactive_triggers into context_extra, including: dwell_threshold_seconds, user_page_dwell_max_actions, dwell_proactive_body, section_playbook, section_url_rules, section_url_fallback_id, current_section_id, and (when rules exist) computed product_section_playbook.
When a user chooses a proactive option
If the proactive message includes quick replies (TriggerMessage rows), this is the canonical structure flow:
- Delivery layer receives selected chip/option id from the user.
- Resolve selected id to
TriggerMessage. - Update
SessionStateinteraction path (for example, record option interaction and keep session routing state current). - If
TriggerMessage.user_tour_existsis true, resolve tour metadata inTourRegistry:- by chip id (
TourDefinition.id) when ids are aligned, or - by
user_tour_id(TourRegistry.get_by_user_tour_id(...)) when using provider flow IDs.
- by chip id (
- Start visual guidance and continue timeout/cooldown handling using session state + tour timing rules.
session_id as primary scope.
Related pages
- Authoring proactive triggers β Step-by-step: build a trigger, build context, register and evaluate, handle delivery.
- Agent session states β
SessionStatev2 gating and idle expiry behavior. - Intercom integration β
quick_replyHTTP shape, delete-conversation helpers. - Typed payloads β
SlimAction,ActionsPayload,SummaryPayload.