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.
BaseChatbotWriter is the delivery engine for any chatbot 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 (recommended)
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 chatbot 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
Pre-link buffering (sliding window)
Before a conversation is linked to a PostHog session, actions are buffered in memory rather than discarded. On every new arrival, entries older thanpre_link_window_s seconds are trimmed β so the buffer never grows unboundedly.
At-link flush (one API call)
Whenon_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, eachwrite_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 chatbot platform.metadataβ open dict for optional extras (user_id,email, β¦).conversation_linked/conversation_idβ set bylink_conversation; the permanent delivery target once set.current_stateβ FSM (THINKING / PROACTIVE / REACTIVE).
Event semantics
| Event | Meaning | SessionState.on_conversation_linked(...) behavior |
|---|---|---|
ConversationEventType.NEW | A newly created conversation for this session | Always set conversation_linked=True and overwrite conversation_id |
ConversationEventType.REPLY_EXISTING | Inbound reply on an existing conversation | Set 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 usingAutoplayChatbotManager, 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 fromformat_chatbot_note_header(session_id, timestamp_unix):
session_id: β¦timestamp: β¦ UTC(human-readable from Unix time)- A blank line after the header
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_startbefore rendering. - Each line is
[n] {description}wherenis 1-based and local to that note. It is not the per-batch wireindexon 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βsbin_seconds(default3). Actions whosetimestamp_startvalues fall in different time bins get a blank line between groups so the note is easier to scan. - Post-link debounced notes:
_format_noteis called withbin_seconds=0, so no extra blank lines between groups.
Example (actions note)
[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.
Constructor
BaseChatbotWriter(product_id, pre_link_window_s=120, post_link_debounce_s=0.15, bin_seconds=3)
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).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.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
SubclassBaseChatbotWriter 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.
API reference
| Method | Description |
|---|---|
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) |
Related
- AgentContextWriter β LLM summarisation and push delivery; pairs with
BaseChatbotWriterfor the full pipeline - Typed payloads β
ActionsPayloadandSlimActionβ the typed models your callbacks receive - Intercom integration β the built-in
BaseChatbotWritersubclass for Intercom - Agent states β
SessionStateFSM reference β THINKING, PROACTIVE, REACTIVE