Skip to main content
BaseChatbotWriter is the delivery engine for any support AI agent integration. Subclass it, implement two async methods, and you get the full delivery policy for free. For the complete session lifecycle (state creation, linking, persistence), use AutoplayChatbotManager β€” it wraps BaseChatbotWriter and handles everything automatically.
AutoplayChatbotManager is the fastest path to a working integration. It owns session state, conversation linking, and delivery β€” you only implement _post_note.
session_id always comes from PostHog (via ActionsPayload.session_id). conversation_id comes from the support AI agent platform webhook. AutoplayChatbotManager marries them and routes all future delivery to that conversation permanently.

Production: Redis-backed persistence

By default, AutoplayChatbotManager uses InMemorySessionStateStore β€” state is lost on restart. For production, pass a RedisSessionStateStore:
SessionState is the complete source of truth β€” FSM state, conversation link, identity, and metadata all persist across restarts. Key pattern: autoplay:session_state:{session_id}. Default TTL: 24 h.

What it does

Before a conversation is linked to a PostHog session, actions are buffered in memory rather than discarded. On every new arrival, entries older than pre_link_window_s seconds are trimmed β€” so the buffer never grows unboundedly.
When on_session_linked() fires, the entire buffer is flushed as a single _post_note call. Actions are grouped into bin_seconds-wide visual bins separated by blank lines β€” making the user’s journey easy to scan. One API call regardless of how many events accumulated before the conversation opened.

Post-link debounce (trailing edge)

After a conversation is linked, each write_actions() call appends to a per-session buffer and (re)schedules a short asyncio.Task. When the timer fires with no new arrivals, one _post_note is made. Rapid event bursts are coalesced; a pause longer than post_link_debounce_s triggers delivery.

Session-first linking

BaseChatbotWriter handles delivery. SessionState is the source of truth for session identity and conversation ownership:
  • session_id β€” required, always set first. Comes from PostHog (ActionsPayload.session_id), never from the support AI agent platform.
  • metadata β€” open dict for optional extras (user_id, email, …).
  • conversation_linked / conversation_id β€” set by link_conversation; the permanent delivery target once set.
  • current_state β€” FSM (THINKING / PROACTIVE / REACTIVE).

Event semantics

EventMeaningSessionState.on_conversation_linked(...) behavior
ConversationEventType.NEWA newly created conversation for this sessionAlways set conversation_linked=True and overwrite conversation_id
ConversationEventType.REPLY_EXISTINGInbound reply on an existing conversationSet link fields only when currently unlinked; do not overwrite an existing linked conversation_id
link_conversation auto-detects the event from the store β€” you do not need to pass it manually.

Canonical dual-write path (low-level)

When not using AutoplayChatbotManager, manage stores directly:
link_conversation writes to ConversationLinkStore first, then calls SessionState.on_conversation_linked(...). The event type is inferred automatically: REPLY_EXISTING if the conversation_id is already in the store, NEW otherwise.

Hot-path routing helper

For delivery decisions, read from session state (no store lookup on the hot path):

Note body format

BaseChatbotWriter builds the string passed to _post_note(conversation_id, body) as plain text, line-oriented.

Header (always)

The first lines come from format_chatbot_note_header(session_id, timestamp_unix):
  • session_id: …
  • timestamp: … UTC (human-readable from Unix time)
  • A blank line after the header
For action notes, the timestamp is taken from the earliest timestamp_start among the actions after sorting. If slim_actions is empty, the header uses the current time instead.

Action lines

  • Actions are sorted by timestamp_start before rendering.
  • Each line is [n] {description} where n is 1-based and local to that note. It is not the per-batch wire index on each slim action dict.
  • One action β†’ one line [1] …. Many β†’ [1] through [n]. Zero actions β†’ header only (no action lines).

Binning

  • Pre-link flush (on_session_linked): uses the constructor’s bin_seconds (default 3). Actions whose timestamp_start values fall in different time bins get a blank line between groups so the note is easier to scan.
  • Post-link debounced notes: _format_note is called with bin_seconds=0, so no extra blank lines between groups.

Example (actions note)

The blank line between [2] and [3] appears when those actions fall in different bin_seconds-wide bins (pre-link flush). Post-link notes omit those separators.

Summary notes (LLM)

_format_note applies only to action timelines. BaseChatbotWriter does not wrap LLM summary text. For overwrite_with_summary, build the body yourself: call format_chatbot_note_header(session_id, time.time()) (or another Unix timestamp), then append your summary prose. The in-repo Intercom integration does this for summary posts.
Import the helper from the package root: from autoplay_sdk import format_chatbot_note_header (also available from autoplay_sdk.chat.chatbot).

Constructor

BaseChatbotWriter(product_id, pre_link_window_s=120, post_link_debounce_s=0.15, bin_seconds=3)

product_id
str
required
Product identifier used in logs and metrics.
How long to retain buffered actions before the session is linked. Actions older than this (measured by timestamp_start) are dropped on each new arrival. Default is 120 seconds (2 minutes).
post_link_debounce_s
float
default:"0.15"
Trailing-edge debounce window in seconds. After the last write_actions() call for a session, the writer waits this long before posting a note. Coalesces rapid event bursts into a single API call. Default is 150 ms.
bin_seconds
int
default:"3"
Width of time bins used to group actions into visual sections in the note body. Actions more than bin_seconds apart get a blank-line separator. Set to 0 to disable binning. Used for the pre-link flush note; post-link notes always use bin_seconds=0.

Building a custom backend

Subclass BaseChatbotWriter and implement these two methods:

_post_note(conversation_id, body) β†’ str | None

Post a note to the platform conversation. Return its platform-assigned id (used for later redaction), or None if the platform does not support redaction.

_redact_part(conversation_id, part_id) β†’ None

Delete or blank a previously posted note. This is called by AsyncAgentContextWriter’s overwrite_with_summary step when LLM summaries are enabled. Implement as a no-op if the platform does not support redaction.

Example β€” Zendesk

Example β€” Generic HTTP endpoint (self-hosted bot bridge)


Using with AsyncAgentContextWriter

BaseChatbotWriter already debounces write_actions() calls internally via post_link_debounce_s. When wiring an AsyncAgentContextWriter to a BaseChatbotWriter subclass, keep debounce_ms=0 (the default) β€” the base class debounce is sufficient, and stacking both windows only adds latency.
Avoid double-debouncing. Setting debounce_ms > 0 on AsyncAgentContextWriter while using a BaseChatbotWriter subclass stacks two debounce windows and adds unnecessary latency with no additional reduction in API calls.

API reference

MethodDescription
write_actions(conversation_id, session_id, slim_actions, ...)Route actions to pre-link buffer or post-link debounce pipeline
on_session_linked(session_id, conversation_id)Store the session→conversation mapping and flush pre-link buffer
_post_note(conversation_id, body)Subclass contract β€” post a note; return its id or None
_redact_part(conversation_id, part_id)Subclass contract β€” delete/blank a posted note (best-effort)
_format_note(session_id, slim_actions, bin_seconds=None)Builds the action-note body described in Note body format above (header, sorted 1-based lines, optional binning)

  • AgentContextWriter β€” LLM summarisation and push delivery; pairs with BaseChatbotWriter for the full pipeline
  • Typed payloads β€” ActionsPayload and SlimAction β€” the typed models your callbacks receive
  • Intercom integration β€” the built-in BaseChatbotWriter subclass for Intercom
  • Agent states β€” SessionState FSM reference β€” THINKING, PROACTIVE, REACTIVE