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

# Changelog

> Version history and release notes for the autoplay-sdk Python package.

The canonical changelog for releases and PyPI sdists also lives in the repository as [`CHANGELOG.md`](https://github.com/Autoplay-AI/real-time-poc/blob/main/later-rho-event-connector/src/customer_sdk/CHANGELOG.md). This page mirrors that file for the documentation site, with links pointed at these docs.

***

## \[0.11.0] — 2026-07-02

### Changed

* **`onboard_product` / `onboard_product_sync` now register against the AWS activity connector.** `DEFAULT_CONNECTOR_URL` is `https://connector.autoplay.ai` (was the legacy Render connector). The `POST /products` request body now carries `provider` + optional `ingest_secret` + `force` (replacing `webhook_secret` / `integration_type=event_stream` / `force_reregister`), and the response is read from `credentials.{ingest_secret, mcp_key}` + `setup.{ingest_url, ingest_auth, mcp_url, mcp_auth}`.
* `OnboardProductResult` reshaped to the AWS model: `product_id`, `provider`, `ingest_url`, `ingest_secret`, `ingest_auth`, `mcp_url`, `mcp_key`, `mcp_auth`, `connector_response`.

### Added

* `build_aws_register_payload` and `post_aws_register_product` in `autoplay_sdk.admin.connector_registration_http`.

### Removed

* Render/SSE-shaped fields from `OnboardProductResult`: `webhook_url`, `stream_url`, `webhook_secret`, `unkey_key`, `unkey_key_id`, `integration_type`, `render_sync_performed`.
* `onboard_product` keyword args `webhook_secret` and `force_reregister` (replaced by `ingest_secret` and `force`); `user_activity_provider` is now required.

### Breaking changes

`onboard_product` targets a different connector with a different request/response contract. **Existing Render customers are unaffected** — they stay on their pinned SDK version and the legacy connector; only new onboarding is routed to AWS. If you call `onboard_product`, use `result.ingest_url` + `result.ingest_secret` to configure your provider's webhook, and `result.mcp_url` + `result.mcp_key` for the agent's MCP read, instead of the old `webhook_url` / `stream_url` / `unkey_key`. The legacy Render onboarding flow remains available in `autoplay_sdk.admin.product_onboarding` / `autoplay_sdk.admin.onboard` for internal Render tooling.

***

## \[0.10.0] — 2026-06-21

### Added

* **`autoplay_sdk.storage`** — a new, self-contained subsystem for cross-session persistence of `UserAdoptionState` plus frozen per-session snapshots, behind a pluggable adapter interface.
  * **`AutoplayStorageAdapter`** — a `@runtime_checkable Protocol` (mirrors `BufferBackend`) with async `save_user_state` / `get_user_state` / `save_session_snapshot` / `get_session_snapshot`. The wire boundary is plain JSON dicts, so custom adapters stay decoupled from the SDK's model classes.
  * **`StorageManager`** — fans writes out to every adapter (one failing sink never blocks the others) and reads from the first adapter that returns a value (list the readable primary first).
  * **`RedisStorageAdapter(redis_url=...)`** — primary read+write adapter; owns a lazy `redis.asyncio` pool with graceful degradation (mirrors `RedisSessionStateStore`), `SET NX` write-once for snapshots. Needs the `redis` extra.
  * **`InMemoryStorageAdapter`** — tests/dev adapter with write-once snapshot semantics.
  * **Product-scoped key builders** — `user_state_key(product_id, user_id)` and `session_key(product_id, user_id, session_id)`.
  * **Session lifecycle helpers** — `init_user_state`, `on_session_start`, `on_session_end`, `build_agent_context`. `on_session_end` resolves the onboarding summary via an injected `summarizer` callable or a pre-computed `summary=` — **the SDK never calls an LLM**.
* **Write-only analytics sinks** — `AmplitudeStorageAdapter` / `PostHogStorageAdapter` (deep-import from `autoplay_sdk.storage.adapters.{amplitude,posthog}`), gated behind a new `autoplay-sdk[analytics]` extra. They `save_*` (with optional `property_prefix=`) and return `None` from `get_*`; failures are best-effort (logged, never raised).
* **`SessionSnapshot`** — a new frozen, write-once per-session model with `to_dict` / `from_dict` (defensive) and a `SessionSnapshot.from_adoption_state(...)` helper that builds the snapshot from a live `UserAdoptionState` (reusing the model's derived helpers — no hand-rolled mapping).
* **`SessionIndexEntry`** + a bounded sessions index on `UserAdoptionState` — `sessions_count` (monotonic lifetime total), `current_session_id`, and `sessions_index` (capped to the most recent 50 via `record_session`), plus `set_current_session`.
* **`OnboardingState.summary`** and **`OnboardingState.completion_rate`** — cross-session briefing + cached completion snapshot.
* **`ONBOARDING_SUMMARY_PROMPT`** (v0.1, plain-text output) in `autoplay_sdk.prompts` and a pure **`build_onboarding_summary_input(snapshot)`** builder in `autoplay_sdk.user_adoption_state`.
* **Exports** — storage symbols re-exported from top-level `autoplay_sdk`; `SessionSnapshot`, `SessionIndexEntry`, `COMPLETION_RATE_DENOMINATOR_*`, and `build_onboarding_summary_input` from `autoplay_sdk.user_adoption_state`. Sink adapters stay deep-import only.
* **`autoplay_sdk.observability`** — a pluggable error-reporter seam (`set_error_reporter` / `report_error`) so a host application can forward SDK-internal failures to its own sink (Slack, Bugsnag, logs, …). The SDK calls `report_error` at its swallow points — RAG embed/upsert, Redis state/snapshot writes, and event-buffer overflow drops — so those otherwise-silent failures become observable. Best-effort and a no-op until a reporter is registered; reporting never raises into SDK logic.

### Documentation

* New page [State storage & session capture](/sdk/storage) covering the three-step integration flow, the adapter/manager model, Redis key schema, write-only sinks, and the injected-summarizer pattern.
* [User adoption state](/sdk/user-adoption-state) persistence note updated to point at the new storage adapters.

### Breaking changes

None — purely additive. The new `UserAdoptionState` fields (`sessions`, `onboarding.summary`, `onboarding.completion_rate`) read with safe defaults, so existing snapshots load unchanged and the `_v` snapshot version is unchanged.

***

## \[0.9.2] — 2026-06-21

### Added

* **Onboarding workflow steps** — each `OnboardingWorkflow` can now declare an ordered list of \*\*`WorkflowStep`\*\*s (`step_id`, `name`, `description`, `required`), configured per product under `integration_config.onboarding_workflows[].steps`. Steps are *context/signal* for the LLM (what "done" looks like) and for explainability — they are **not** a checklist gate. `required` is a hint, not a hard gate. Helper `OnboardingWorkflow.required_step_ids`.
* **`StepProgress`** — the agent's per-step belief (`step_id`, `status` reusing `WorkflowStatus`, `confidence`, `evidence`, `completed_at`, `last_evaluated_at`), a deliberate parallel to `WorkflowProgress` one level down.
* **`WorkflowProgress.steps`** — a `dict[str, StepProgress]` map of per-step beliefs, with a `completed_step_ids` helper and an `upsert_step_progress(step_id, *, status, confidence, evidence="", now=None)` mutation (write-once `completed_at`, mirroring `upsert_workflow_progress`).
* **`is_plan_complete(plan, workflow_progress)`** — thin convenience wrapper over `split_workflows` (`True` when no outstanding workflows; an empty plan is never complete). Completion stays the LLM's call via `WorkflowProgress.status` — steps never gate it.
* **`step_completion_ratio(workflow, wp)`** — fraction of a workflow's declared steps the LLM believes are `completed`. For display/telemetry only; never used to decide completion.
* **Exports** — `WorkflowStep`, `StepProgress`, `is_plan_complete`, `step_completion_ratio` added to `autoplay_sdk.user_adoption_state`.

### Documentation

* [User adoption state](/sdk/user-adoption-state) updated with the steps data model, the product-config `steps` schema, and the completion semantics (LLM-authoritative; steps are signal, not a gate).

### Breaking changes

None — purely additive. `steps` is read with safe defaults, so existing snapshots (no `steps` key) load unchanged and the `_v` snapshot version is unchanged.

***

## \[0.9.1] — 2026-06-16

### Added

* **Amplitude activity provider support** — adds the SDK provider wiring and activity skill needed to configure Amplitude as an event source.

### Documentation

* Expanded the setup and quickstart docs for activity providers, agent skills, MCP server setup, and Intercom tutorial assets.

***

## \[0.9.0] — 2026-06-12

### Added

* **`PostHogProvider.create_destination()` + `.verify()`** — deterministic, idempotent creation (and verification) of the PostHog → Autoplay "Autoplay Event Stream" hog\_function destination, built on the provider abstraction. Powers the one-command `autoplay-setup` onboarding flow (plumbing runs as tested code, not via an agent).

### Changed

* **`activity-posthog` skill is now the single source** (modular: `SKILL.md` + `references/` + `examples/`), replacing the forked `posthog-setup` skill the setup bundle used to ship. Scoped to *frontend code only* (the CLI now owns destination create/verify and the end-to-end event check). Identify uses the app's **stable user id** on login (never the anonymous id) + `reset()` on logout.

***

## \[0.8.0] — 2026-06-09

### Added

* **`autoplay_sdk.user_adoption_state`** — a new per-user adoption-state module, orthogonal to the per-session `SessionState` FSM and keyed by `user_id`. Tracks where a user is in their product lifecycle and how proficient they are, decided by an LLM judge and fed back into chat / proactive prompts.
* **Enums** — `JourneyState` (`onboarding` / `onboarded`), `MasteryLevel` (`novice` → `beginner` → `intermediate` → `proficient` → `power_user`), and `WorkflowStatus` (`not_started` / `in_progress` / `completed`).
* **`UserAdoptionState`** — top-level per-user record: a fully dynamic, schema-less `metadata` bag (with a `role` convenience property), `journey_state`, `mastery`, `discovered_features`, and an `onboarding` sub-object. Mandatory non-empty `user_id` (raises otherwise, like `SessionState`); `to_dict()` / `from_dict()` with `_v` snapshot versioning and defensive enum coercion; transition helpers (`set_journey_state`, `set_mastery`, `record_discovered_features`, `mark_welcomed`, `mark_judged`) that log structured events; and `to_prompt_block()` for compact prompt injection.
* **`Mastery`** (0-10 rating, clamped, + `MasteryLevel` + reason), **`WorkflowProgress`** (per-flow `status` / `confidence` / `evidence` / timestamps — the agent's evolving belief from the live activity stream), **`StepInAssessment`** (latest "should I step in?" decision + timing rationale), and **`OnboardingState`** (welcome lifecycle, `workflow_progress` map, derived `completed` / `in_progress` / `outstanding` helpers, `upsert_workflow_progress`, `record_step_in`).
* **`ExplorationGateConfig`** + **`evaluate_hard_gate()`** — pure, side-effect-free "let the user explore first" floor (role allowlist, min time on app, min distinct features, welcome cooldown, nudge cap). Returns `(passed, reason)`; the LLM owns timing on top of the floor.
* **`OnboardingWorkflow`** / **`OnboardingPlan`** (parsed from config, role-filterable via `for_role`) + pure **`split_workflows()`** → `(completed, in_progress, outstanding)`.
* **Query → tour matching primitive** — **`build_tour_match_input()`** and **`parse_tour_match_output()`** (validates ids against a role-filtered catalog, safe fallback on malformed JSON), plus **`TourCatalogEntry`** and **`TourMatchResult`**.
* **Versioned prompts** in `autoplay_sdk.prompts` (each a `dict` with `name` / `description` / `version` / `content`, JSON-object output): **`ADOPTION_STATE_JUDGE_PROMPT`**, **`TOUR_MATCH_PROMPT`**, **`ONBOARDING_WELCOME_PROMPT`** (all v0.1).
* **Top-level exports** from `autoplay_sdk`: `user_adoption_state`, `UserAdoptionState`, `JourneyState`, `MasteryLevel`, `WorkflowStatus`.

### Documentation

* New [User adoption state](/sdk/user-adoption-state) reference page covering the two axes (journey vs mastery), the full state model, the explore-first gate, onboarding plans, and the query → tour matcher. The SDK ships pure logic + prompt definitions only; persistence and `acall_llm` runners are integration-owned.

### Breaking changes

None — the module and exports are purely additive.

***

## \[0.7.9] — 2026-05-25

### Breaking changes

* `SessionState.session_id` is now a **required** field — no default. `SessionState()` raises `TypeError`. Use `SessionState(session_id="...")` or `InMemorySessionStateStore.get_or_create(session_id)`. `from_dict()` still handles old snapshots gracefully via `.get("session_id", "")`.

### Added

* **`SessionState.can_deliver_proactive() -> tuple[bool, str]`** — canonical FSM gate check.
  Returns `(True, "ok")` when the session is THINKING with no active cooldown; `(False, reason)` otherwise.
  Callers must call `tick()` before this method. Replaces any inline `current_state == THINKING and not thinking.cooldown_active` pattern.
* **`SessionState`** now holds `session_id: str` (the only mandatory scope key) and `metadata: dict[str, Any]` (open bag for optional identity context — `user_id`, `email`, any future fields). Both fields are persisted via `to_dict()` / `from_dict()` and default to empty for backward compatibility.
* **`RedisSessionStateStore`** — Redis-backed `SessionState` store for production use. Key pattern `autoplay:session_state:{session_id}`, configurable TTL (default 24 h), graceful fallback on Redis errors. Requires `pip install "autoplay-sdk[redis]"`.
* **`AutoplayChatbotManager`** — high-level wrapper that handles all session lifecycle automatically. Developers implement only `_post_note` and call two methods: `on_actions(payload)` from the Autoplay stream and `on_chatbot_event(session_id, conversation_id)` from the chatbot webhook.
* **`AutoplayChatbotManager(session_store=...)`** — optional parameter to inject a custom store. Defaults to `InMemorySessionStateStore()`. Pass `RedisSessionStateStore(redis_url=...)` for production.
* **`AutoplayChatbotManager.save_state(state)`** — public method for persisting state after external FSM transitions (`transition_to_proactive`, `transition_to_reactive`, `tick`, `start_cooldown`, interaction recorders). `on_actions` and `on_chatbot_event` call this automatically; use `save_state` only for mutations that happen outside those entry points.
* **`InMemorySessionStateStore`** — canonical store for `SessionState` objects. `get_or_create(session_id)` is the first call in every handler. `async` interface for drop-in Redis-backed replacement.
* **`BaseChatbotWriter.__init_subclass__` enforcement** — raises `TypeError` at class-definition time (module import) when a subclass sets `SESSION_LINK_WEBHOOK_TOPICS` without overriding `_parse_session_link_webhook_payload`. Converts a silent production crash into a startup error caught by tests and local runs. Subclasses that leave `SESSION_LINK_WEBHOOK_TOPICS = ()` are unaffected.
* `link_conversation` now auto-detects `ConversationEventType.NEW` vs `REPLY_EXISTING` from the store when `event` is not passed — removes the last manual determination from integration authors.
* `link_conversation` now infers `session_id` from `state.session_id` when not passed explicitly. Existing callers that pass `session_id` are unaffected.
* `link_conversation` raises `ValueError` if `session_id` is empty after resolution, with a clear message pointing to `InMemorySessionStateStore.get_or_create`.
* `AutoplayChatbotManager` and `InMemorySessionStateStore` exported from top-level `autoplay_sdk`.
* `AutoplayChatbotManager.on_chatbot_event` now persists the updated `SessionState` back to `RedisSessionStateStore` after linking so the conversation link survives restarts.
* `redis` optional extra added to `pyproject.toml`: `pip install "autoplay-sdk[redis]"`.

### Changed

* **`ConversationEvent` renamed to `ConversationEventType`** — the old name was misleading because it read as a data container rather than a type discriminator. A deprecated backward-compat alias `ConversationEvent = ConversationEventType` is exported from both `autoplay_sdk` and `autoplay_sdk.chat`; it will be removed in the next major version. Update any import of `ConversationEvent` to `ConversationEventType`.
* **`proactive_fsm_gate_for_session` delivery invariant clarified** — no linked conversation → first proactive → always allowed (a new conversation will be created and linked). A linked conversation → check FSM via `can_deliver_proactive()`. The `fallback_conversation_id` parameter has been removed.
* **Polling gates use `session.can_deliver_proactive()` directly** — polling paths (`context_payload.py`, `routes.py`) load and tick the session for the known `conversation_id`, then call `can_deliver_proactive()` on the already-loaded session. They no longer go through `proactive_fsm_gate_for_session` (which has delivery-specific semantics).
* `AutoplayChatbotManager.on_chatbot_event` removes the `isinstance(store, RedisSessionStateStore)` guard — `save` is always called regardless of which store is wired. `on_actions` does not save (no state mutation occurs during action delivery; first-creation is already persisted by `get_or_create`).
* `InMemorySessionStateStore` gains a no-op `save(state)` method so all call sites are uniform.

### Documentation

* [BaseChatbotWriter](/sdk/support-agent-writer): rewritten to lead with `AutoplayChatbotManager` as the recommended path, updated session-first linking section to show `InMemorySessionStateStore` + `get_or_create` pattern, added Production persistence section with `RedisSessionStateStore` one-line swap.
* `autoplay-core` skill: added **Integration Self-Reasoning Checklist** — 5 categories agents must reason through before an integration is complete: session identity, race conditions, chatbot linking, proactive triggers, and production persistence. Section 4 updated to use `can_deliver_proactive()` as the canonical gate check and fixes a stale method reference (`to_proactive` → `transition_to_proactive`). Added **Universal Scoping Pattern** — the canonical 4-step flow (scope → parse webhook → link → deliver) that applies identically to every chatbot integration.
* `SessionState` class docstring now lists `conversation_linked` and `conversation_id` in its Fields section.
* [Agent session states](/sdk/agent-states) Key Methods table now includes `can_deliver_proactive()`; `ThinkingState` table now includes `active_cooldown_period_s`.

***

## \[0.7.8] — 2026-05-21

### Changed

* Bumped package version to `0.7.8`.

***

## \[0.7.7] — 2026-05-21

### Changed

* Bumped package version to `0.7.7`.
* Fixed circular import between `autoplay_sdk.integrations.intercom` and `autoplay_sdk.proactive.triggers` by moving `proactive_trigger_canonical_url_ping_pong` and `proactive_trigger_canonical_url_ping_pong_projects_either_leg` into `autoplay_sdk.proactive.triggers._url_predicates`. `intercom.py` re-exports both for backward compatibility.

***

## \[0.7.6] — 2026-05-18

### Fixed

* **`autoplay_sdk/skills/autoplay-migrate-imports/SKILL.md`** — tightened the discovery `rg` filter to require a word boundary (`(\.|\s)`) after the canonical-domain alternation group. Without it, deprecated sub-modules whose names start with a canonical prefix (`context_store`, `proactive_triggers`, `agent_state_v2`, `rag_query`) were silently excluded from results, leaving 4 of 6 legacy imports unflagged.

### Changed

* **`autoplay_sdk/skills/autoplay-migrate-imports/SKILL.md`** — inlined the full legacy→canonical path mapping table directly in the skill file. The previous wording ("see the changelog mapping table") referenced a doc that does not ship inside the wheel, so agents had no static replacement guidance and had to rely on runtime `DeprecationWarning` messages to discover new paths.

### Documentation

* Updated [BaseChatbotWriter](/sdk/support-agent-writer) with a new **session-first linking** section covering:
  * `ConversationEventType.NEW` vs `ConversationEventType.REPLY_EXISTING`
  * canonical dual-write flow via `link_conversation(...)` (`ConversationLinkStore` write, then `SessionState.on_conversation_linked(...)`)
  * hot-path routing with `resolve_linked_conversation_id(state)` from session-owned state.
* Expanded [Agent session states](/sdk/agent-states) v2 docs with:
  * explicit `SessionState` link fields (`conversation_linked`, `conversation_id`)
  * `on_conversation_linked(link)` overwrite/no-op semantics and invariants
  * a state/flag matrix for `THINKING`, `PROACTIVE`, and `REACTIVE`.
* Added cross-SDK data-structure mapping in [Typed payloads](/sdk/typed-payloads) and [Payload schema](/sdk/payload-schema), clarifying boundaries between session state, link state, webhook parse models, and stream payloads.

***

## \[0.7.5] — 2026-05-17

### Changed

* Bumped package version to `0.7.5`.
* Expanded the `test` optional dependency extra to include async/runtime test dependencies (`pytest-asyncio`, `fakeredis`, `respx`) so `pip install -e ".[serve,test]"` is enough for SDK CI and local test runs.
* Enabled `asyncio_mode = "auto"` in pytest config so async tests are discovered consistently without requiring explicit per-test markers.

***

## \[0.7.4] — 2026-05-16

### Summary

Domain-oriented internal reorganization with one-release compatibility shims. Old import paths still work in `0.7.4` and emit `DeprecationWarning`; they are removed in `1.0.0`.

### Added

* `autoplay_sdk.compat` shim registry and helper.
* `tests/compat/test_import_compat.py` import parity tests.
* CI workflow for import compatibility across Python 3.10/3.11/3.12.
* Migration skill: `autoplay-migrate-imports`.

### Changed

* Canonical import domains now use `core`, `chat`, `context`, `proactive`, `agent_state`, `api`, `rag`.
* `autoplay-install-skills` now supports `--migrate`.

### Deprecated paths (remove in 1.0.0)

`autoplay_sdk.rag` remains a stable alias in `0.7.4` (no deprecation warning).

| Old                                                                   | New                                                                   |
| --------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `autoplay_sdk.serve`                                                  | `autoplay_sdk.api`                                                    |
| `autoplay_sdk.serve.fastapi`                                          | `autoplay_sdk.api.fastapi`                                            |
| `autoplay_sdk.agent_states`                                           | `autoplay_sdk.agent_state.v1.states`                                  |
| `autoplay_sdk.agent_states.state_machine`                             | `autoplay_sdk.agent_state.v1.state_machine`                           |
| `autoplay_sdk.agent_states.types`                                     | `autoplay_sdk.agent_state.v1.types`                                   |
| `autoplay_sdk.agent_states.proactive_idle_expiry`                     | `autoplay_sdk.proactive.state.idle_expiry`                            |
| `autoplay_sdk.agent_state_v2`                                         | `autoplay_sdk.agent_state.v2.states`                                  |
| `autoplay_sdk.agent_state_v2.session_state`                           | `autoplay_sdk.agent_state.v2.session_state`                           |
| `autoplay_sdk.agent_state_v2.types`                                   | `autoplay_sdk.agent_state.v2.types`                                   |
| `autoplay_sdk.rag_query`                                              | `autoplay_sdk.rag.query`                                              |
| `autoplay_sdk.rag_query.assembly`                                     | `autoplay_sdk.rag.query.assembly`                                     |
| `autoplay_sdk.rag_query.formatters`                                   | `autoplay_sdk.rag.query.formatters`                                   |
| `autoplay_sdk.rag_query.pipeline`                                     | `autoplay_sdk.rag.query.pipeline`                                     |
| `autoplay_sdk.rag_query.watermark`                                    | `autoplay_sdk.rag.query.watermark`                                    |
| `autoplay_sdk.proactive_resilience`                                   | `autoplay_sdk.proactive.resilience.resilience`                        |
| `autoplay_sdk.proactive_resilience.circuit`                           | `autoplay_sdk.proactive.resilience.circuit`                           |
| `autoplay_sdk.proactive_resilience.config`                            | `autoplay_sdk.proactive.resilience.config`                            |
| `autoplay_sdk.proactive_resilience.keys`                              | `autoplay_sdk.proactive.resilience.keys`                              |
| `autoplay_sdk.proactive_resilience.outcomes`                          | `autoplay_sdk.proactive.resilience.outcomes`                          |
| `autoplay_sdk.proactive_resilience.protocol`                          | `autoplay_sdk.proactive.resilience.protocol`                          |
| `autoplay_sdk.chatbot`                                                | `autoplay_sdk.chat.chatbot`                                           |
| `autoplay_sdk.chat_pipeline`                                          | `autoplay_sdk.chat.chat_pipeline`                                     |
| `autoplay_sdk.context_store`                                          | `autoplay_sdk.context.context_store`                                  |
| `autoplay_sdk.agent_context`                                          | `autoplay_sdk.context.agent_context`                                  |
| `autoplay_sdk.user_index`                                             | `autoplay_sdk.context.user_index`                                     |
| `autoplay_sdk.summarizer`                                             | `autoplay_sdk.context.summarizer`                                     |
| `autoplay_sdk.models`                                                 | `autoplay_sdk.core.models`                                            |
| `autoplay_sdk.exceptions`                                             | `autoplay_sdk.core.exceptions`                                        |
| `autoplay_sdk.metrics`                                                | `autoplay_sdk.core.metrics`                                           |
| `autoplay_sdk.onboarding`                                             | `autoplay_sdk.admin.onboarding`                                       |
| `autoplay_sdk.proactive_idle_expiry`                                  | `autoplay_sdk.proactive.state.idle_expiry`                            |
| `autoplay_sdk.proactive_triggers`                                     | `autoplay_sdk.proactive.triggers`                                     |
| `autoplay_sdk.proactive_triggers.builtin_catalog`                     | `autoplay_sdk.proactive.triggers.builtin_catalog`                     |
| `autoplay_sdk.proactive_triggers.context_source`                      | `autoplay_sdk.proactive.triggers.context_source`                      |
| `autoplay_sdk.proactive_triggers.defaults`                            | `autoplay_sdk.proactive.triggers.defaults`                            |
| `autoplay_sdk.proactive_triggers.entity`                              | `autoplay_sdk.proactive.triggers.entity`                              |
| `autoplay_sdk.proactive_triggers.judge`                               | `autoplay_sdk.proactive.triggers.judge`                               |
| `autoplay_sdk.proactive_triggers.pending_tour_offer`                  | `autoplay_sdk.proactive.triggers.pending_tour_offer`                  |
| `autoplay_sdk.proactive_triggers.predicate_trigger`                   | `autoplay_sdk.proactive.triggers.predicate_trigger`                   |
| `autoplay_sdk.proactive_triggers.proactive_intercom_config`           | `autoplay_sdk.proactive.triggers.proactive_intercom_config`           |
| `autoplay_sdk.proactive_triggers.quick_reply_match`                   | `autoplay_sdk.proactive.triggers.quick_reply_match`                   |
| `autoplay_sdk.proactive_triggers.registry`                            | `autoplay_sdk.proactive.triggers.registry`                            |
| `autoplay_sdk.proactive_triggers.scope`                               | `autoplay_sdk.proactive.triggers.scope`                               |
| `autoplay_sdk.proactive_triggers.section_activity`                    | `autoplay_sdk.proactive.triggers.section_activity`                    |
| `autoplay_sdk.proactive_triggers.tour_registry`                       | `autoplay_sdk.proactive.triggers.tour_registry`                       |
| `autoplay_sdk.proactive_triggers.trigger_config`                      | `autoplay_sdk.proactive.triggers.trigger_config`                      |
| `autoplay_sdk.proactive_triggers.triggers`                            | `autoplay_sdk.proactive.triggers.triggers`                            |
| `autoplay_sdk.proactive_triggers.triggers.canonical_ping_pong`        | `autoplay_sdk.proactive.triggers.triggers.canonical_ping_pong`        |
| `autoplay_sdk.proactive_triggers.triggers.scoped_canonical_ping_pong` | `autoplay_sdk.proactive.triggers.triggers.scoped_canonical_ping_pong` |
| `autoplay_sdk.proactive_triggers.triggers.section_playbook`           | `autoplay_sdk.proactive.triggers.triggers.section_playbook`           |
| `autoplay_sdk.proactive_triggers.triggers.user_page_dwell`            | `autoplay_sdk.proactive.triggers.triggers.user_page_dwell`            |
| `autoplay_sdk.proactive_triggers.types`                               | `autoplay_sdk.proactive.triggers.types`                               |
| `autoplay_sdk.proactive_triggers.url_scope`                           | `autoplay_sdk.proactive.triggers.url_scope`                           |

See [Migration 0.7.4](/sdk/migration-0.7.4) for upgrade steps.

***

## \[0.7.3] — 2026-05-14

### Documentation

* Added dedicated references for [`UserSessionIndex`](/sdk/user-session-index), [`compose_chat_pipeline(...)`](/sdk/compose-chat-pipeline), and [`build_copilot_app(...)`](/sdk/build-support-agent-app), including lifecycle details, endpoint status semantics, and failure troubleshooting.
* Updated [Logging](/sdk/logging) with explicit observability guidance for `autoplay_sdk.chat_pipeline`, `autoplay_sdk.user_index`, and `autoplay_sdk.serve.fastapi`, including recommended app-layer log points for self-hosted bridges.
* Added a clearer self-hosted quick-reference and troubleshooting section in `README.md` (repository package docs), covering primitive selection and common `404`/identity/product-scope failure modes.

***

## \[0.7.2] — 2026-05-14

### Removed

* **`TourDefinition.label`** and **`TourDefinition.user_tour_exists`** — these fields belong to `proactive_intercom.messages`, not the tour registry. Removed from the dataclass, `to_dict`, and `from_dict`. Legacy configs that still include these keys are silently ignored on parse.

### Added

* **`TOUR_OFFER_QUICK_REPLY_BODY`** — constant (`"Would you like me to show you?"`); default body for the tour-offer Yes/No quick reply.
* **`tour_offer_quick_reply_body(integration_config)`** — single SDK source-of-truth for the tour-offer message body; reads `integration_config.tour_offer_body` for product-level overrides, falls back to the constant. Call this after `resolve_tour_offer_for_inbound` returns a non-`None` flow id. Both symbols exported from `autoplay_sdk.proactive_triggers`.

***

## \[0.7.1] — 2026-05-13

### Added

* **`autoplay_sdk.install_skills`** — CLI command `autoplay-install-skills` that copies bundled Cursor/Claude agent skills into the current project. Supports `--chatbot` and `--user-activity` flags.
* **`autoplay_sdk/skills/`** — 10 agent skill files shipped inside the wheel: `autoplay-core`, `chatbot-ada`, `chatbot-intercom`, `chatbot-botpress`, `chatbot-dify`, `chatbot-crisp`, `chatbot-landbot`, `chatbot-tidio`, `activity-fullstory`, `activity-posthog`.

### Documentation

* Ada tutorial (`docs/recipes/ada/`), FullStory Streams tutorial (`docs/recipes/fullstory/how-to-setup.mdx`), and quickstart `autoplay-install-skills` tip block.

***

## \[0.7.0] — 2026-05-12

### Added

* **`autoplay_sdk.agent_state_v2`** — `SessionState` FSM with `THINKING` / `PROACTIVE` / `REACTIVE` states, timeout-only exit rules, and `InvalidTransitionError`. Coexists with v1 `AgentStateMachine`.
* **`autoplay_sdk.proactive_triggers.trigger_config`** — `ProactiveTriggerConfig`, `TriggerMessage`, and recursive `ProactiveCriteria` for config-driven proactive triggers.
* **`autoplay_sdk.proactive_triggers.tour_registry`** — `TourDefinition` and `TourRegistry`; per-tour `interaction_timeout_s` / `cooldown_period_s` overrides; `get_by_user_tour_id()`.
* **`SessionState.record_tour_step()`**, **`SessionState.set_visual_guidance()`**, **`SessionState.tick(tour_registry=None)`**.
* **`parse_tour_registry(integration_config, product_id)`** helper in `proactive_intercom_config`.

### Changed

* **`integration_config.proactive_intercom`** — now a **list** of `ProactiveTriggerConfig` objects (was a single dict).
* **`TriggerMessage`** — renamed `offers_tour` → `user_tour_exists`, `flow_id` → `user_tour_id`; backward-compatible properties retained.

### Documentation

* [Intercom proactive triggers step 2](/recipes/intercom-tutorial/step-2-define-proactive-triggers), [Agent session states](/sdk/agent-states) — agent state v2 accordion.

***

## \[0.6.8] — 2026-05-05

* SDK branch release tag/version for current branch cut.
* `AgentStateMachine.enter_reactive_from_user_message` docs + behavior notes now include rejection logging and preserved `InvalidTransitionError` semantics.

***

## \[0.6.7] — 2026-04-30

Agent FSM, Intercom proactive **`quick_reply`**, **`autoplay_sdk.proactive_triggers`** (replaces **`integrations.proactive`**), registry timings/entity, context builders, scope validation, product-scoped context-store buckets, and proactive idle teardown for chat: **`run_proactive_idle_expiry`**, **`ProactiveIdleExpiryHooks`**, **`ProactiveIdleExpiryResult`**, Intercom **`DELETE /conversations/{id}`** helpers (**`build_intercom_delete_conversation_request`**, etc.).

For full **Added** / **Changed** / **Breaking** detail, see the same version in the repository [`CHANGELOG.md`](https://github.com/Autoplay-AI/real-time-poc/blob/main/later-rho-event-connector/src/customer_sdk/CHANGELOG.md).

### Removed

* **`autoplay_sdk.integrations.proactive`** — use **`autoplay_sdk.proactive_triggers`** (`import` paths and submodules mirror the old layout).

### Added

* **`autoplay_sdk.agent_states`** — **`AgentState`**, **`AgentStateMachine`**, **`TaskProgress`**, **`SessionMetrics`**, **`InvalidTransitionError`**, **`InvalidSnapshotError`**; five-state FSM; **`transition_on_disengagement()`**; **`to_snapshot()`** / **`from_snapshot()`**; **`can_show_proactive_with_reason()`**.
* **`autoplay_sdk.agent_states.AgentStateMachine`** — **`expire_proactive_to_thinking_if_idle`**.
* **`autoplay_sdk.integrations.intercom`** — **`quick_reply`** helpers, **`INTERCOM_PROACTIVE_QUICK_REPLY_DEFAULT_BODY`**, **`proactive_trigger_canonical_url_ping_pong`**, connector helpers for **`POST /intercom/proactive/{product_id}`**, **`IntercomProactivePolicyConfig`**.
* **`autoplay_sdk.proactive_triggers`** — **`ProactiveTriggerContext`**, **`ProactiveTriggerResult`**, registry, **`CanonicalPingPongTrigger`**, **`default_proactive_trigger_registry`**, timings (**`ProactiveTriggerTimings`**, **`ProactiveTriggerEntity`**, **`interaction_timeout_s`**, **`cooldown_s`**).
* **`autoplay_sdk.proactive_triggers.defaults`** — **`ProactiveTriggerIds`**, **`get_proactive_trigger_ids()`**, **`DEFAULT_PROACTIVE_QUICK_REPLY_BODY`**.
* **`PredicateProactiveTrigger`**; **`from_actions_payloads`** / **`from_slim_actions`**; **`DEFAULT_PROACTIVE_CONTEXT_*`** exports; **`scope`** (**`ScopePolicy`**, validation helpers); **`validate_scope`** (**`require_conversation`**).
* **`autoplay_sdk.context_store`** — **`actions_bucket_id`**, optional **`product_id`** on **`get`**, **`enrich`**, **`reset`**; composite **`_actions`** keys when **`ActionsPayload.product_id`** is set.

### Changed

* **`autoplay_sdk.integrations.intercom`** — **`INTERCOM_PROACTIVE_QUICK_REPLY_DEFAULT_BODY`** aliases **`proactive_triggers.defaults`**.
* **`ProactiveTriggerContext`** — optional **`recent_actions`**, summaries, **`context_extra`**.
* **`from_slim_actions` / `from_actions_payloads`** — default **`scope_policy=STRICT`** (use **`LENIENT`** to relax).
* **`POST /intercom/proactive/{product_id}`** documented as always-on when authenticated (**`ENABLE_INTERCOM_PROACTIVE_PROMPTS`** removed).

### Documentation

* [Agent session states](/sdk/agent-states), [Intercom integration](/integrations/intercom), [Proactive triggers](/sdk/proactive-triggers), [Authoring proactive triggers](/sdk/proactive-triggers-authoring).

### Breaking changes

* **`expire_proactive_to_thinking_if_idle`** — returns **`ProactiveIdleExpiryResult`** (use **`if result:`** / **`result.transitioned`**, not **`is True`** on the return value). See [`CHANGELOG.md`](https://github.com/Autoplay-AI/real-time-poc/blob/main/later-rho-event-connector/src/customer_sdk/CHANGELOG.md#067--2026-04-30).
* Migrate imports from **`autoplay_sdk.integrations.proactive`** to **`autoplay_sdk.proactive_triggers`**.
* Strict scope defaults; **`LENIENT`** for old permissive behaviour.
* Match **`product_id`** on context-store reads when payloads carry **`product_id`**.

***

## \[0.6.6] — 2026-04-23

### Added

* **`SlimAction`** / **`ActionsPayload`** — optional **`conversation_id`** (Intercom thread when linked); merge semantics unchanged at batch level.

* **`autoplay_sdk.rag_query`** — query-time RAG assembly (**`assemble_rag_chat_context`**, providers, **`ChatContextAssembly`**, formatters).

* **`autoplay_sdk.prompts`** — versioned Adoption Copilot defaults (**`RAG_SYSTEM_PROMPT`**, **`REASONING_PROMPT`**, **`RESPONSE_PROMPT`**).

* **`autoplay_sdk.rag_query.watermark`** — delta activity watermarks for Intercom / connector chat.

* **`autoplay_sdk.prompts.intercom_readability`** — shared **`INTERCOM_READABILITY_RULES`** fragment for Messenger replies.

### Changed

* Root **`__all__`** documents query-time RAG vs ingestion **`RagPipeline`**.

* **`RAG_SYSTEM_PROMPT`** / **`RESPONSE_PROMPT`** (1.2) and connector **`INTERCOM_CHAT_PROMPT`** (5) — readability + guidance framing for product how-to questions.

* **`assemble_rag_chat_context`** — structured DEBUG / WARNING logging.

### Documentation

* [Support AI agent context assembly](/sdk/support-agent-context-assembly) — delta activity and observability cross-links.

### Breaking changes

None for JSON consumers — new fields remain optional.

***

## \[0.6.4] — 2026-04-23

### Added

* **`SlimAction`** — optional per-action `session_id`, `user_id`, and `email` (aligned with batch-level payload identity). Present on each entry in `actions` for SSE, push webhooks, and typed parsing.

* **`RedisEventBuffer`** — serialized JSON now includes those fields per action so buffered events round-trip cleanly.

### Documentation

* [Payload schema](/sdk/payload-schema) and [Typed payloads](/sdk/typed-payloads) — document per-action identity fields; payload schema clarifies shared shape for SSE and push webhooks.

### Breaking changes

None — new fields are optional (`None` when omitted).

***

## \[0.6.2] — 2026-04-16

### Added

* **`POST /products` terminal feedback** — TTY-only Rich stderr spinner and stdout success/failure lines in `post_register_product_payload`; no output when stdout is not a TTY.

### Changed

* **`run_product_onboarding`** — single `POST /products`; connector performs Redis + Render when configured. `render_sync_performed` reflects connector `dual_write` in the JSON response.
* **`unkey.py` is now a default dependency** — plain `pip install autoplay-sdk` includes operator onboarding (`autoplay_sdk.admin` / Unkey).

### Removed

* **`autoplay-sdk[admin]` extra** — install `autoplay-sdk` only.

***

## \[0.6.1] — 2026-04-14

### Added

* **`ConversationEventType`** (`autoplay_sdk.chatbot`) — enum for chatbot session-link event semantics (`NEW` / `REPLY_EXISTING`).
* **`BaseChatbotWriter` session-link webhook flow** — `SESSION_LINK_WEBHOOK_TOPICS`, `extract_conversation_event()`, `_parse_session_link_webhook_payload()` (subclasses override parse only).
* **`autoplay_sdk.integrations.intercom`** — webhook topic constants, `intercom_chatbot_webhook_url()`, optional `format_reactive_session_link_script` (no logging from this subpackage).

### Removed

* **`format_proactive_session_start_script`** and the proactive **`/sessions/start`** snippet — use Intercom webhooks to `POST /chatbot-webhook/{product_id}`; optional **`format_reactive_session_link_script`** remains for `POST /sessions/link`.

### Documentation

* [Logging](/sdk/logging) — logger hierarchy, app-owned `logging` configuration, third-party subclass guidance, changelog cross-link.
* README — logging section aligned with the above; metrics pointer (`SdkMetricsHook`).
* [Intercom integration](/integrations/intercom) — SDK helpers and connector mapping (webhooks, optional snippet, inbox UX).

### Breaking changes

* **`format_proactive_session_start_script`** removed from `autoplay_sdk.integrations.intercom`.

### Deprecations

None.

***

## \[0.6.0] — 2026-04-13

### Documentation

* **BaseChatbotWriter — Note body format** — [BaseChatbotWriter](/sdk/support-agent-writer) now documents the full plain-text contract for `_post_note` bodies (header via `format_chatbot_note_header`, sorted 1-based action lines, binning vs post-link, empty list, summary notes). `_format_note` docstring points to that page as the single source of truth.

* **Logging** — New [Logging](/sdk/logging) reference page (module loggers, `%` formatting, `exc_info`, structured `extra`, secrets guidance including HTTP bodies, common logging mistakes). [Quickstart](/quickstart) links to it for discoverability.

### Bug fixes / observability

* `BaseChatbotWriter` — pre-link flush failure `warning` now includes structured `extra` (`session_id`, `product_id`, `conversation_id`).

* `BaseChatbotWriter` — post-link debounced flush: if `_post_note` returns no part id after the debounce buffer was popped, logs a `warning` with the same `extra` shape and explains that this flush is not retried automatically.

### Breaking changes

None.

### Deprecations

None.

***

## \[0.5.0] — 2026-04-10

### New features

* **`ActionsPayload.merge(payloads)`** — class method that merges a non-empty list of `ActionsPayload` objects for the same session into one. Actions are concatenated and re-indexed from `0`; `user_id`/`email` resolved from the first non-`None` value; `forwarded_at` set to the latest timestamp. Raises `ValueError` on empty input.

* **`AsyncAgentContextWriter(debounce_ms=N)`** — new optional constructor parameter for a per-session trailing-edge accumulation window. When `> 0`, multiple `add()` calls arriving within the window are merged via `ActionsPayload.merge()` before `write_actions` is called, reducing destination API calls during event bursts. Default is `0` (no debounce — existing behaviour unchanged).

* **`BaseChatbotWriter`** (`autoplay_sdk.chatbot`) — new public base class providing the complete pre-link/post-link delivery policy for building chatbot destinations. Subclass it and implement `_post_note` and `_redact_part`; pre-link buffering (sliding window), at-link flush (binned note), and post-link debouncing are all included. `IntercomChatbot` in the event connector already extends this class.

### Breaking changes

None. All changes are additive:

* `AsyncAgentContextWriter.__init__` gains `debounce_ms: int = 0` — existing code passing positional or keyword arguments is unaffected.
* `ActionsPayload.merge()` is a new class method; no existing method is renamed or removed.
* `BaseChatbotWriter` is a new public export; no existing symbols are removed.

### Deprecations

None.

### Bug fixes / error handling improvements

* `BaseChatbotWriter.on_session_linked` — now no-ops when the same `conversation_id` is passed again (idempotent guard). The product worker includes the conv\_id on every batch for already-linked sessions; without this guard, each batch would cancel the in-flight 150ms post-link debounce task and restart the window, causing notes to be delayed indefinitely during fast user interactions.

* `BaseChatbotWriter.on_session_linked` — pre-link buffer (`_pending`) is now only cleared after `_post_note` confirms success (returns a non-`None` part id). Previously the buffer was popped before the API call; a transient Intercom failure would permanently lose those events. On failure the buffer is now preserved and the `_conv_map` entry is rolled back so the next `on_session_linked` call retries automatically.

* `BaseChatbotWriter.write_actions`: the post-link debounce `asyncio.Task` now has a `done_callback` that logs any unhandled exception at `ERROR` level with structured `extra` (previously silent — Python only emitted a `DEBUG`-level "Task exception was never retrieved").

* `AsyncAgentContextWriter._flush_session` done-callback: now includes `product_id` in the log message and `extra` dict for structured log filtering.

### Migration notes

**`AsyncAgentContextWriter` + `BaseChatbotWriter` — avoid double-debouncing**

`BaseChatbotWriter` already coalesces rapid `write_actions()` calls via its `post_link_debounce_s` window (default 150 ms). When wiring an `AsyncAgentContextWriter` to a `BaseChatbotWriter` subclass, keep `debounce_ms=0` (the default):

```python theme={null}
# CORRECT — BaseChatbotWriter handles debouncing; no stacking needed
writer = AsyncAgentContextWriter(
    summarizer=summarizer,
    write_actions=chatbot_subclass.write_actions_cb,
    overwrite_with_summary=overwrite_cb,
    debounce_ms=0,   # ← default, explicit for clarity
)

# AVOID — stacks two debounce windows, adds latency without benefit
writer = AsyncAgentContextWriter(..., debounce_ms=200)
```

Use `debounce_ms > 0` only when `write_actions` points to a raw destination with no internal coalescing (e.g. a direct Zendesk or Salesforce API call).

***

## \[0.4.0] — 2026-04-09

### Bug fixes

* Fixed TOCTOU race in `RedisEventBuffer._get_redis()`: concurrent callers could each create their own connection pool; now serialised with `asyncio.Lock` and double-checked locking.
* Fixed `AsyncSessionSummarizer.flush()` cancellation safety: replaced sequential `for q in queues: await q.join()` with `asyncio.gather(*[asyncio.shield(q.join()) for q in queues])` so all queues are drained even when the caller is cancelled.

### Other

* Added `CHANGELOG.md` to document breaking changes and new features going forward.

***

## \[0.3.0] — 2026-04-09

### Breaking changes

* **`AsyncSessionSummarizer.get_context(session_id)` is now `async`** — callers must `await` it.
* **`AsyncSessionSummarizer.reset(session_id)` is now `async`** — callers must `await` it.
* **`AsyncSessionSummarizer.active_sessions` is now an `async` property** — callers must `await` it.
* **`AsyncSessionSummarizer.add()` now returns immediately** — the LLM call is dispatched to a background worker queue rather than awaited inline. Code that relied on the LLM having completed by the time `await add()` returned must call `await summarizer.flush()` before inspecting state.

### New features

* **`AsyncSessionSummarizer.flush()`** — waits for all queued payloads to be fully processed; cancellation-safe via `asyncio.gather` + `asyncio.shield`.
* **`SdkMetricsHook` protocol** (`autoplay_sdk.metrics`) — a `@runtime_checkable Protocol` that customers can implement to receive Prometheus / Datadog / OTEL counters for: dropped events, summarizer latency, Redis operation latency, queue depth, and semaphore timeouts.
* **`metrics=` constructor parameter** on `ConnectorClient`, `AsyncConnectorClient`, `AsyncSessionSummarizer`, and `RedisEventBuffer`.
* **`initial_backoff_s`, `max_backoff_s`, `max_retries` constructor parameters** on both SSE clients — exposes and documents the reconnect policy with configurable jitter-backed exponential backoff.
* **Per-session ordering guarantee in `AsyncSessionSummarizer`** — each session now has its own `asyncio.Queue` + background `asyncio.Task` worker, ensuring that concurrent `add()` calls for the same session are always processed in arrival order, even if an earlier LLM call fails.
* **`py.typed` marker** — the package is now PEP 561-compliant; static type-checkers will find type stubs automatically.

### Bug fixes (v0.2.x → v0.3.0)

The following 24 items were addressed across two audit passes:

**Concurrency & correctness**

* Fixed `AsyncSessionSummarizer` ordering bug: concurrent adds during an LLM failure could produce out-of-order `on_summary` callbacks (replaced single lock with per-session queue).
* Fixed TOCTOU race in `RedisEventBuffer._get_redis()`: concurrent callers could each create their own connection pool; now serialised with `asyncio.Lock` and double-checked locking.
* Replaced `asyncio.Semaphore._value` (private API, breaks across CPython minor versions) with an explicit `_TrackedSemaphore` counter.
* Replaced `asyncio.get_event_loop()` (deprecated) with `asyncio.get_running_loop()` in `app.py` and `async_client.py`.
* Replaced `asyncio.ensure_future()` with `loop.create_task()` in `async_client.py`.
* Added `done_callback` on fire-and-forget `asyncio.Task`s to log unhandled exceptions instead of silently swallowing them.

**Error handling**

* `RedisEventBuffer._payload_from_json()` now wraps `json.loads` in `try/except json.JSONDecodeError`; corrupt ZSET members no longer crash the drain loop.
* `ConnectorClient` now sets `self._running = False` on `KeyboardInterrupt` so callers can inspect the state after shutdown.
* All `except` clauses that were swallowing exceptions now pass `exc_info=True` to the logger so tracebacks appear in structured logs.

**Data structures**

* `RedisEventBuffer` ZSET members now carry a unique UUID prefix, preventing silent deduplication when two events arrive at the same millisecond timestamp.
* `SessionSummarizer` now deletes `_history[session_id]` and `_counts[session_id]` after summarisation to prevent unbounded memory growth.
* `AsyncConnectorClient._session_semaphores` changed from plain `dict` to `collections.OrderedDict` with LRU eviction to cap memory when many short-lived sessions are processed.

**Redis connection management**

* Extracted `LazyRedisClient` helper (`storage/_redis.py`) so all storage modules share one lazily-initialised, thread-safe Redis client instead of each implementing the same racy pattern.
* `session_store` now uses `LazyRedisClient` and exposes a `SessionState.from_redis_link()` classmethod that owns the full reconstruction logic (preventing silent field omissions on restore).
* `SessionState` gains an `error: Optional[str]` field to surface last-known error reason through the API.

**Logging & observability**

* Replaced custom `_JsonFormatter` in `app.py` that ignored `extra={}` fields with a correct implementation that merges them into the JSON line.
* All structured log calls now use `extra={}` dicts consistently.
* Metrics instrumentation added at every observability-relevant site: event drops, queue depth, semaphore timeouts, summarizer latency, Redis add/drain latency.

**Package hygiene**

* Added `__all__` exports to `__init__.py` so `from autoplay_sdk import *` is well-defined.
* Added `__version__ = "0.3.0"` to `__init__.py`.
* Added `py.typed` marker for PEP 561 compliance.
* Removed dead `_dropped_count` / `_total_count` metrics fields that were incremented but never surfaced.
* Standardised public API: `on_drop` callback signature is now consistent across `ConnectorClient`, `AsyncConnectorClient`, and `RedisEventBuffer`.

***

## \[0.2.0] — prior

Initial internal release. No changelog maintained at this version.
