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β aProtocol; 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 β
RedisStorageAdapterandInMemoryStorageAdapterare built in. The write-only analytics sinks live inautoplay_sdk.storage.adapters.{amplitude,posthog}.
Integration: three steps you own
1. Configure adapters once
pip install "autoplay-sdk[analytics]"):
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 liveUserAdoptionState, 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.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 theProtocol β no inheritance required. The wire boundary is plain JSON dicts (model.to_dict()), so your adapter never depends on the SDKβs model classes.
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.