Skip to main content
Use autoplay_sdk.storage to persist a user’s UserAdoptionState across sessions and to freeze an immutable session snapshot at the end of each session. The agent reads this history at the next session start to support the user continuously.

What it is

Two storage tiers, both behind one pluggable interface: The mutable record is the latest view of the user; the immutable snapshots are the durable per-session history the agent reasons over.
The SDK never calls an LLM. The onboarding summary is produced by your acall_llm runner and passed into on_session_end (via an injected summarizer callable or a pre-computed summary=). The SDK ships the versioned prompt (ONBOARDING_SUMMARY_PROMPT) and a pure input builder (build_onboarding_summary_input); you own the execution.

Architecture

  • AutoplayStorageAdapter β€” a Protocol; implement it to target any destination.
  • StorageManager β€” fans writes out to every adapter and reads from the first one that returns a value. List your readable primary (Redis) first.
  • Adapters β€” RedisStorageAdapter and InMemoryStorageAdapter are built in. The write-only analytics sinks live in autoplay_sdk.storage.adapters.{amplitude,posthog}.

Integration: three steps you own

1. Configure adapters once

Add write-only analytics sinks alongside Redis (needs pip install "autoplay-sdk[analytics]"):
For tests and local dev, swap in InMemoryStorageAdapter().

2. On session start β€” load state and onboarding context

on_session_start loads the user’s state (or initializes a new one), marks the current session, and β€” for returning users β€” fetches the onboarding (session 1) snapshot so the agent has continuity.

3. On session end β€” freeze a snapshot and refresh state

The SDK builds the snapshot for you from the live UserAdoptionState, so you write near-zero mapping code:
on_session_end writes the snapshot once (a retried session-end can never overwrite it), refreshes the mutable user state, and appends the session to the index.

The injected summarizer

The SDK ships the prompt and a pure input builder; you run the LLM and hand the result back. This keeps the SDK free of any LLM dependency.
Prefer a pre-computed value? Pass summary="..." instead of summarizer=; it takes precedence. If you pass neither, the summary is simply left unchanged. A summarizer that raises is logged and never loses the session.

Custom adapters

Any object with the four async methods satisfies the Protocol β€” no inheritance required. The wire boundary is plain JSON dicts (model.to_dict()), so your adapter never depends on the SDK’s model classes.
Write-only destinations (e.g. analytics) implement the save_* methods and return None from get_*; reads are served by the primary (Redis) adapter.

Built-in adapters

Both sinks accept a property_prefix= to namespace the properties they write (e.g. property_prefix="ap" β†’ ap_journey_state).

Scaling notes

UserAdoptionState keeps a bounded inline sessions_index (the most recent 50 sessions) plus a monotonic sessions_count, so the per-session-start read stays small. Full history is never lost β€” every session is also its own frozen snapshot key, retrievable by session_id.