> ## 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.

# Agent session states

> SessionState v2 FSM for reactive chat, proactive offers, cooldown gating, and session-owned conversation routing state.

Use **`autoplay_sdk.agent_state.v2`** (`SessionState`) as the only supported agent-state model.

## SessionState v2

## Overview

**`autoplay_sdk.agent_state.v2`** provides the supported three-state FSM and enforces a clear timeout-only exit rule from active states. Timeouts and cooldowns are session-level settings rather than per-trigger values.

### States

| Wire value             | Python enum              | Meaning                                                                                    |
| ---------------------- | ------------------------ | ------------------------------------------------------------------------------------------ |
| `thinking`             | `AgentStateV2.THINKING`  | Idle baseline. Proactive offers may fire — unless the cooldown period is still active.     |
| `proactive_assistance` | `AgentStateV2.PROACTIVE` | The assistant surfaced an unsolicited quick-reply offer. User has not yet responded.       |
| `reactive_assistance`  | `AgentStateV2.REACTIVE`  | The user opened the support AI agent or sent a message; the session is in active dialogue. |

### Transition rules

| From                                             | To                     | How                                   | Notes                                                                                                                         |
| ------------------------------------------------ | ---------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `thinking`                                       | `proactive_assistance` | `transition_to_proactive(trigger_id)` | Returns `False` (no state change) if `cooldown_active` is still `True`. Flushes any expired cooldown first.                   |
| `thinking`                                       | `reactive_assistance`  | `transition_to_reactive()`            | Always allowed from `THINKING`. Clears cooldown — user initiating a chat cancels the proactive backoff period.                |
| `proactive_assistance`                           | `thinking`             | `tick()` only — automatic             | **The only exit.** Triggered when `now − last_interaction_at > interaction_timeout_s`. Always starts the cooldown on arrival. |
| `reactive_assistance`                            | `thinking`             | `tick()` only — automatic             | **The only exit.** Same timeout rule as above.                                                                                |
| `proactive_assistance`                           | `reactive_assistance`  | ❌ Never                               | Raises `InvalidTransitionError`.                                                                                              |
| `reactive_assistance`                            | `proactive_assistance` | ❌ Never                               | Raises `InvalidTransitionError`.                                                                                              |
| any → `proactive_assistance` when not `THINKING` | ❌ Never                | Raises `InvalidTransitionError`.      |                                                                                                                               |
| any → `reactive_assistance` when not `THINKING`  | ❌ Never                | Raises `InvalidTransitionError`.      |                                                                                                                               |

<Note>
  `_timeout_to_thinking()` is a private method called exclusively by `tick()`. Never call it directly in your code — use `tick()` on every incoming event or background pulse instead.
</Note>

### Session-level timeout settings

| Field                   | Default | Meaning                                                                                                                                                                      |
| ----------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `interaction_timeout_s` | `20 s`  | How long the user can go without interacting with the support AI agent or visual guidance before returning to `thinking`. Applies in both `proactive` and `reactive` states. |
| `cooldown_period_s`     | `60 s`  | After returning to `thinking` from any active state, how long before proactive can fire again. Auto-clears when the period elapses.                                          |

### Session-owned routing fields

These fields are persisted directly on `SessionState` and are the canonical delivery routing source:

| Field                 | Default | Meaning                                                                  |
| --------------------- | ------- | ------------------------------------------------------------------------ |
| `conversation_linked` | `False` | Whether this `session_id` is currently linked to a conversation          |
| `conversation_id`     | `None`  | Derived destination thread id used for hot-path send routing when linked |

`SessionState.on_conversation_linked(link)` transition rules:

* `ConversationEventType.NEW`: always set `conversation_linked=True` and overwrite `conversation_id`
* `ConversationEventType.REPLY_EXISTING`: set link fields only when currently unlinked; keep existing `conversation_id` when already linked
* invariant: during a live session, this path never flips `conversation_linked` back to `False`

### State and flag matrix

| State       | Purpose                   | Key fields typically read/updated                                                         | Routing implication                                                                 |
| ----------- | ------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `THINKING`  | Baseline idle state       | `thinking.cooldown_active`, `last_interaction_at`                                         | If `conversation_linked` is true, route to `conversation_id`; otherwise create/link |
| `PROACTIVE` | Assistant initiated offer | `proactive.user_clicked_option`, `last_interaction_at`, `visual_guidance_active`          | Same routing rule: state does not change session-owned conversation link            |
| `REACTIVE`  | User-initiated chat       | `reactive.time_since_last_interaction_s`, `last_interaction_at`, `visual_guidance_active` | Same routing rule: session-owned link remains the send target                       |

### Per-state sub-dataclasses

Each state carries a typed sub-object that tracks its own metrics. These are reset when the session enters that state.

**`ThinkingState`**

| Field                      | Type            | Meaning                                                                                                                                                |
| -------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `cooldown_active`          | `bool`          | `True` while the cooldown period is blocking a new proactive offer.                                                                                    |
| `cooldown_started_at`      | `float`         | Monotonic timestamp of when the cooldown began.                                                                                                        |
| `active_cooldown_period_s` | `float \| None` | Tour-specific cooldown override. When set, this period is used instead of the session-level `cooldown_period_s`. `None` means use the session default. |
| `can_go_proactive`         | property        | `True` when `cooldown_active` is `False`.                                                                                                              |

**`ProactiveState`**

| Field                           | Type    | Meaning                                                                                                                       |
| ------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `user_clicked_option`           | `bool`  | `True` once the user taps a quick-reply chip.                                                                                 |
| `time_since_last_interaction_s` | `float` | Seconds since the last interaction of **any** kind — chat message, option click, reaction, or tour step. Updated by `tick()`. |

**`ReactiveState`**

| Field                           | Type    | Meaning                                                   |
| ------------------------------- | ------- | --------------------------------------------------------- |
| `time_since_last_interaction_s` | `float` | Seconds since last user interaction. Updated by `tick()`. |

### Public exports

```python theme={null}
from autoplay_sdk.agent_state.v2 import (
    AgentStateV2,
    InvalidTransitionError,
    SessionState,
    ThinkingState,
    ProactiveState,
    ReactiveState,
)
```

<Note>
  Import `InvalidTransitionError` from `autoplay_sdk.agent_state.v2` with the rest of the v2 symbols.
</Note>

### Key methods

| Method                                | Description                                                                                                                                                                                                                                                                                                  |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `tick()`                              | Evaluate all timeout rules. Call on every incoming event and on a background pulse (e.g. every 5 s) to catch silent timeouts.                                                                                                                                                                                |
| `can_deliver_proactive()`             | Returns `(allowed: bool, reason: str)` — the canonical FSM gate check. Call `tick()` first. Returns `(True, "ok")` when `THINKING` with no active cooldown, `(False, "v2_state_<state>")` when not in THINKING, `(False, "v2_cooldown_active")` when cooldown is active. Do not replicate this check inline. |
| `transition_to_proactive(trigger_id)` | Move to `PROACTIVE`. Returns `False` if cooldown is active; raises `InvalidTransitionError` if not in `THINKING`.                                                                                                                                                                                            |
| `transition_to_reactive()`            | Move to `REACTIVE`. Raises `InvalidTransitionError` if not in `THINKING`. Clears cooldown.                                                                                                                                                                                                                   |
| `record_any_interaction()`            | Reset `last_interaction_at`. Call on any support AI agent or visual guidance activity.                                                                                                                                                                                                                       |
| `record_user_interaction()`           | User sent a chat message.                                                                                                                                                                                                                                                                                    |
| `record_option_click()`               | User clicked a quick-reply chip.                                                                                                                                                                                                                                                                             |
| `record_reaction()`                   | User added an emotional reaction.                                                                                                                                                                                                                                                                            |
| `record_tour_step()`                  | Any tour progress (page advance, step complete). Resets the `interaction_timeout_s` countdown even when the user has never opened the chat — visual guidance counts as interaction.                                                                                                                          |
| `set_visual_guidance(active)`         | Toggle `visual_guidance_active` on the current state. Starting a tour (`active=True`) also resets `last_interaction_at`. No-op in `THINKING`.                                                                                                                                                                |
| `on_conversation_linked(link)`        | Apply canonical `NEW` vs `REPLY_EXISTING` link semantics to `conversation_linked` / `conversation_id`.                                                                                                                                                                                                       |
| `to_dict()` / `from_dict(d)`          | Round-trip JSON snapshot for Redis or any store. `from_dict` raises `ValueError` on wrong `_v`.                                                                                                                                                                                                              |

### Persistence

```python theme={null}
# Save
await save_session(product_id, session_id, session)

# Load
session = await load_session(product_id, session_id)
session.tick()  # flush any elapsed timeouts before acting
```

Session scope is primary: persist/load by `session_id`, and treat `conversation_id` as derived session-owned routing state.

### Example

```python theme={null}
from autoplay_sdk.agent_state.v2 import (
    AgentStateV2,
    InvalidTransitionError,
    SessionState,
)

# Start fresh
session = SessionState(interaction_timeout_s=20.0, cooldown_period_s=60.0)

# Proactive trigger fires
fired = session.transition_to_proactive("trig_001")
assert fired is True
assert session.current_state == AgentStateV2.PROACTIVE

# User clicks a chip
session.record_option_click()

# Background pulse — flush timeouts
session.tick()

# Simulate 25 s of silence → transitions back to thinking with cooldown
session.last_interaction_at -= 25.0
session.tick()
assert session.current_state == AgentStateV2.THINKING
assert session.thinking.cooldown_active is True

# During cooldown, proactive is blocked
assert session.transition_to_proactive("trig_002") is False

# After cooldown expires, proactive is allowed again
session.thinking.cooldown_started_at -= 70.0
session.tick()
assert session.thinking.can_go_proactive is True

# Persist
blob = session.to_dict()
restored = SessionState.from_dict(blob)
```

## See also

* [Support AI agent context assembly](/sdk/support-agent-context-assembly) — query-time RAG assembly (`rag_query`).
* [Proactive onboarding agent](/sdk/proactive-onboarding-agent) — product framing for proactive experiences.
* [Logging](/sdk/logging) — **`autoplay_sdk.agent_state.v2.session_state`** logger and related `event_type` keys.
