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

# UserSessionIndex

> User-keyed session stitching for mapping one user to recent product sessions and reading cross-session activity safely.

Use `UserSessionIndex` when your chat layer is keyed by `user_id` but product events arrive keyed by `session_id`.

It tracks recent session refs per user and preserves `product_id` so reads stay product-scoped.
This gives you a stable bridge from session-keyed live events to the right user context before the app binds chat turns to a `conversation_id`.

`session_id` scoping remains the compulsory base path. `UserSessionIndex` is an
optional identity layer for deployments that capture `user_id`.

## Import

```python theme={null}
from autoplay_sdk.user_index import UserSessionIndex
```

## Constructor

```python theme={null}
UserSessionIndex(
    context_store,
    lookback_seconds=300.0,
    max_sessions_per_user=20,
)
```

* `context_store`: `ContextStore` or `AsyncContextStore` used for per-session retrieval.
* `lookback_seconds`: max age of tracked sessions before eviction.
* `max_sessions_per_user`: hard cap of session refs retained per user.

## Minimal setup

```python theme={null}
from autoplay_sdk import AsyncContextStore
from autoplay_sdk.user_index import UserSessionIndex

context_store = AsyncContextStore()
session_index = UserSessionIndex(
    context_store,
    lookback_seconds=300,
    max_sessions_per_user=20,
)

async def on_actions(payload):
    await context_store.add(payload)
    session_index.add(payload)
```

## Methods

* `add(payload)` — update the index from one `ActionsPayload`.
* `add_async(payload)` — async alias useful for async callback wiring.
* `get_recent_sessions(user_id)` — returns newest-first `SessionRef` rows.
* `get_email(user_id)` — returns the last known email cached from payloads.
* `get_user_activity(user_id)` — joins activity by calling `context_store.get(session_id, product_id=...)` across recent sessions.
* `reset_user(user_id)` — clears session refs and cached email for the user.

## Behavior guarantees

* Missing `session_id` payloads are ignored.
* Missing `user_id` payloads are ignored by the index (no user linkage is
  created), while session-scoped event storage can still continue elsewhere in
  your pipeline.
* Session refs are deduplicated per user and sorted by `last_seen_at` (newest first).
* Expired refs are evicted on read and update; memory usage stays bounded.
* Product-scoped retrieval is preserved by storing `product_id` in each `SessionRef`.

## When identity is absent

If your session replay provider does not capture `user_id`, keep the compulsory
session-scoped path (`session_id` -> `conversation_id`) as your baseline.
`UserSessionIndex` can be introduced later once identity capture is available.

## Common pitfalls

* Identity drift between event source IDs and chat user IDs causes empty reads.
* If actions are product-scoped, always preserve `product_id` when retrieving context.
