Skip to main content

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.

Consume Autoplay’s real-time event stream on your server and inject live user activity into Ada’s AI Agent via metaFields — so every conversation is grounded in what the user is actually doing in your product.

✨ Final result

User: Is there a way to automatically notify my team when a task is done?

Ada: Yes! You can use Automations for this. Go to the Automations centre
on your board and set up a rule like "When status changes to Done, notify someone."

User: Where is the Automations centre?
No idea the user has already done this manually 9 times. Forces a follow-up.
How this works — FullStory Streams sends click events to Autoplay in real time via webhook. Autoplay batches those events (~3 s bins) on your backend, builds a live activity summary per session, and serves it to the Ada SDK as metaFields before — or during — a chat session. Ada’s AI Agent reads these variables inside your bot’s Processes to personalise responses.

📋 Prerequisites

Before starting, confirm the following:
  • Ada already integrated on your web app — the Ada embed script is installed, your bot handle is configured, and Ada is opening chat sessions successfully.
  • FullStory already set up — complete the FullStory setup tutorial first. FullStory Streams must be configured and sending click events to Autoplay before this tutorial will work.
  • Autoplay connector credentials — stream URL and API token from the Autoplay dashboard (output of the FullStory setup).
  • autoplay-sdk installedpip install autoplay-sdk
  • An LLM client — any provider (OpenAI, Anthropic, Gemini, Mistral, etc.). The SDK accepts any async callable that takes a prompt string and returns a string.
How session identity works — Autoplay keys sessions by the FullStory session ID. In the browser this is available via await FS('getSessionAsync', { format: 'id' }). This is the same value stored as the key in the backend context store. When the user opens Ada, the frontend fetches this ID, calls your context endpoint, and passes the result to Ada as metaFields.FS('getSessionAsync') waits for FullStory capture to be fully initialised before resolving — always use the async version, not FS('getSession'), which returns null before FullStory bootstraps.If a user is anonymous (not yet identified), FullStory still assigns a session ID — Autoplay will have context for that session, but it won’t be linked to a user account until FS('setIdentity', { uid: '...' }) is called.

The building blocks

  1. Define Variables in Ada — Create the four Variables your bot Processes will read. Done once in the Ada dashboard.
  2. Consume the Autoplay stream & build a context store — Open Autoplay’s SSE stream on your backend. Use AsyncAgentContextWriter to accumulate and summarise events per session into a Redis-backed store.
  3. Serve context to the Ada SDK — Expose a lightweight authenticated endpoint your frontend calls before opening Ada. Pass the result as metaFields.
  4. Use Variables in your Ada Processes (Step 2 — coming soon) — Reference the injected variables in your bot’s Process conditions and responses.

🔧 Step 1 — Define Variables in Ada

When you pass metaFields to the Ada embed, those keys become Variables your bot Processes can read. Create these in your Ada dashboard under Build → Variables → + New Variable.
Variable name (= metaFields key)TypeDescription
session_idStringFullStory session ID — keys the backend context store
current_pageStringURL path the user is on when they open chat
recent_actionsStringLast ~3 s of user actions as a bullet list
session_summaryStringLLM-generated summary after 20+ actions
Ada metaFields keys must not include whitespace, emojis, special characters, or periods. Use underscores as shown above. Keys are case-sensitive and must match exactly between the backend store and the frontend metaFields object.

📡 Step 2 — Consume the stream & build a context store

Your backend opens Autoplay’s SSE stream and uses AsyncAgentContextWriter to process events. Context is written to a Redis store keyed by session_id with a 2-hour TTL; your API endpoint (Step 3) reads from it.
A module-level dict loses all context on every deploy and breaks across multiple backend instances. Use Redis so context survives restarts and works at scale. See the Redis asyncio docs.

Install dependencies

pip install autoplay-sdk openai fastapi uvicorn redis[asyncio]

Context store + callbacks

# ada_context.py
import json
from dataclasses import dataclass, asdict
import redis.asyncio as redis

# Replace with your Redis URL (e.g. from env var)
redis_client = redis.from_url("redis://localhost:6379", decode_responses=True)
SESSION_TTL = 60 * 60 * 2  # 2 hours

@dataclass
class AdaContext:
    session_id: str
    current_page: str = ""
    recent_actions: str = ""
    session_summary: str = ""

async def save_context(ctx: AdaContext) -> None:
    await redis_client.setex(
        f"ada_ctx:{ctx.session_id}",
        SESSION_TTL,
        json.dumps(asdict(ctx)),
    )

async def get_context(session_id: str) -> AdaContext:
    raw = await redis_client.get(f"ada_ctx:{session_id}")
    if raw:
        return AdaContext(**json.loads(raw))
    return AdaContext(session_id=session_id)

# ── Callbacks wired into AsyncAgentContextWriter ──────────────

async def write_actions_cb(session_id: str, text: str) -> None:
    """Called ~every 3 s with a formatted action list."""
    ctx = await get_context(session_id)
    ctx.recent_actions = text

    # Extract current page from most recent navigation action
    for line in reversed(text.splitlines()):
        if "navigated to" in line.lower() or "visited" in line.lower():
            ctx.current_page = line.strip()
            break

    await save_context(ctx)

async def overwrite_cb(session_id: str, summary: str) -> None:
    """Called after 20 actions with an LLM summary."""
    ctx = await get_context(session_id)
    ctx.session_summary = summary
    ctx.recent_actions = ""  # clear raw actions once summarised
    await save_context(ctx)

Stream worker

# stream_worker.py
import asyncio
import openai
from autoplay_sdk import AsyncConnectorClient, AsyncSessionSummarizer
from autoplay_sdk.agent_context import AsyncAgentContextWriter
from ada_context import write_actions_cb, overwrite_cb

CONNECTOR_URL = "https://your-connector.onrender.com/stream/your-product-id"
API_TOKEN     = "your-api-token"

# Wire your preferred LLM — any async callable that takes a prompt and returns a string.
# OpenAI example shown; swap the body for Anthropic, Gemini, Mistral, etc.
async_openai = openai.AsyncOpenAI()

async def llm(prompt: str) -> str:
    r = await async_openai.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.3,
        max_tokens=256,
    )
    return r.choices[0].message.content

agent_writer = AsyncAgentContextWriter(
    summarizer=AsyncSessionSummarizer(llm=llm, threshold=20),
    write_actions=write_actions_cb,
    overwrite_with_summary=overwrite_cb,
    debounce_ms=0,
)

async def run_stream():
    async with AsyncConnectorClient(url=CONNECTOR_URL, token=API_TOKEN) as client:
        client.on_actions(agent_writer.add)
        await client.run()

if __name__ == "__main__":
    asyncio.run(run_stream())

🌐 Step 3 — Serve context to the Ada SDK

FastAPI context endpoint

# api.py
from fastapi import FastAPI
from ada_context import get_context

app = FastAPI()

@app.get("/context/{session_id}")
async def context_for_session(session_id: str):
    ctx = await get_context(session_id)
    return {
        "session_id":      ctx.session_id,
        "current_page":    ctx.current_page,
        "recent_actions":  ctx.recent_actions,
        "session_summary": ctx.session_summary,
    }
Protect this endpoint in production. Validate with a session cookie or short-lived signed token tied to the logged-in user — not the raw session_id alone, which would let any caller read any user’s context.

Add the Ada embed script

Add data-lazy to your Ada embed script. This prevents Ada from initialising until you call adaEmbed.start(), so you can fetch FullStory context first and pass it in on the first open.
<!-- In <head>. Replace YOUR-BOT-HANDLE. -->
<script
  id="__ada"
  data-handle="YOUR-BOT-HANDLE"
  data-lazy
  src="https://static.ada.support/embed2.js"
></script>

Open Ada with live context

// support.js

// ── Session identity ──────────────────────────────────────────────────────
// Use FS('getSessionAsync') — NOT FS('getSession'). The sync version returns
// null before FullStory finishes bootstrapping; the async version waits.
async function getFullStorySessionId() {
  try {
    return await FS('getSessionAsync', { format: 'id' });
  } catch {
    return 'anonymous';
  }
}

// ── Ada initialisation state ──────────────────────────────────────────────
// Do NOT use `window.adaEmbed` to check if Ada is ready.
// With data-lazy, window.adaEmbed exists as soon as the embed script loads —
// BEFORE start() is called. Calling setMetaFields() or toggle() at that point
// silently fails and the widget never opens.
// Use this flag instead, set only inside adaReadyCallback.
let adaReady = false;

// ── Main: wire to your "Contact Support" button ───────────────────────────
async function openAdaWithContext() {
  const sessionId = await getFullStorySessionId();

  let meta = {
    session_id:      sessionId,
    current_page:    window.location.pathname,
    recent_actions:  '',
    session_summary: '',
  };

  try {
    const res = await fetch(`/context/${sessionId}`);
    if (!res.ok) throw new Error(`Context endpoint ${res.status}`);
    const ctx = await res.json();
    meta = { ...meta, ...ctx };
  } catch (e) {
    // Fail open — Ada opens without extra context rather than not opening at all
    console.warn('Autoplay context unavailable:', e);
  }

  if (adaReady) {
    // Ada already initialised — update metaFields in-place then toggle open
    await window.adaEmbed.setMetaFields(meta);
    await window.adaEmbed.toggle();
    return;
  }

  // First open — initialise Ada with metaFields pre-loaded
  await window.adaEmbed.start({
    handle: 'YOUR-BOT-HANDLE',
    metaFields: meta,
    adaReadyCallback: () => {
      adaReady = true;           // mark ready BEFORE toggling
      window.adaEmbed.toggle();
    },
  });
}

// ── SPA: update current_page on every navigation ─────────────────────────
// React Router and other SPA frameworks navigate via history.pushState(),
// which does NOT fire the popstate event. Patch pushState to emit a custom
// event so all navigations are caught — not just browser back/forward.
(function patchHistory() {
  const _push = history.pushState.bind(history);
  history.pushState = function (...args) {
    _push(...args);
    window.dispatchEvent(new Event('spa:navigate'));
  };
})();

window.addEventListener('spa:navigate', async () => {
  if (!adaReady) return;
  await window.adaEmbed.setMetaFields({ current_page: window.location.pathname });
});

// ── Ada event subscriptions ───────────────────────────────────────────────
// adaSettings must be defined BEFORE the <script> tag that loads embed2.js
// to guarantee onAdaEmbedLoaded fires before any events are triggered.
window.adaSettings = {
  onAdaEmbedLoaded: () => {
    // Stop pushing context updates once a live agent joins —
    // the human agent already has the context they need
    window.adaEmbed.subscribeEvent('ada:agent:joined', () => {
      window.removeEventListener('spa:navigate', () => {});
    });
    window.adaEmbed.subscribeEvent('ada:end_conversation', (data) => {
      console.log('Ada conversation ended:', data.chatter_id);
    });
  },
};

📋 What the context looks like

The recent_actions string Autoplay builds from FullStory events (Ada reads this as a Variable):
session_id: fs_8226444501427639735
timestamp: 2025-03-11 09:14:22 UTC

[1] User navigated to /boards/8821034/views/table
[2] User clicked "Add view" button on the board toolbar
[3] User clicked "Timeline" in the Add view panel
[4] User navigated to /boards/8821034/views/timeline
[5] User clicked "Set dates" button on item "Q2 Feature Launch"
[6] User clicked "Timeline column" on item "Q2 Feature Launch"
[7] User clicked "⚡ Automate" button on the board toolbar
And a session_summary (produced after 20 actions):
The user opened their Sprint Planning board in Table view, switched to Timeline
view for the first time, and attempted to set dates on the "Q2 Feature Launch"
item. They clicked the Timeline column twice without successfully setting a date
range, suggesting they are unfamiliar with the date-picker interaction. They then
opened the Automations centre, browsed for approximately 90 seconds without
creating a rule, and opened the chat widget.

📣 Useful Ada events to subscribe to

Wire these in onAdaEmbedLoaded for guaranteed delivery:
Event keyWhenUseful for
ada:end_conversationUser closes or ends chatClear or archive session context on the backend
ada:agent:joinedLive agent joinsStop pushing setMetaFields updates (human agent has context)
ada:agent:leftLive agent leavesResume setMetaFields updates
ada:conversation:messageNew message receivedPush a fresh context snapshot via setMetaFields
ada:minimize_chatUser minimises chatPause non-critical context updates

Next: Step 2 — Define proactive triggers (coming soon)Step 2 will cover using Autoplay’s proactive trigger system to automatically open Ada with a targeted greeting when a user performs a high-signal action — for example, surfacing a retention message when recent_actions indicates cancellation intent.