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

# Step 1 — Connect real-time events

> Pull a user's live activity from Autoplay on demand and inject it into Ada's AI Agent via metaFields.

## ⚡ Add this skill

<CardGroup cols={2}>
  <Card title="One command" icon="terminal">
    Add the Autoplay Ada skill for an existing Ada AI support agent setup.

    <CodeGroup>
      ```bash CLI theme={null}
      uvx --from autoplay-sdk autoplay-install-skills --chatbot ada
      ```
    </CodeGroup>

    <a className="skill-card-link" href="/recipes/ada/step-1-connect-real-time-events">View the docs →</a>
  </Card>

  <Card title="Agent onboarding" icon="robot">
    Fetch this skill when a customer already uses Ada and wants its AI support agent to consume Autoplay live user activity.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -s https://developers.autoplay.ai/chatbot-ada/SKILL.md
      ```
    </CodeGroup>

    <a className="skill-card-link" href="https://developers.autoplay.ai/chatbot-ada/SKILL.md" target="_blank">View the skill →</a>
  </Card>
</CardGroup>

Pull a user's live activity from Autoplay on demand and inject it into Ada's AI Agent via `metaFields` — so every conversation is grounded in what the user is actually doing in your product.

## ✨ Final result

<Tabs>
  <Tab title="Without real-time context">
    ```text theme={null}
    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.*
  </Tab>

  <Tab title="With real-time context">
    ```text theme={null}
    Live context: Board 4452109 · status → "Done" × 9 · no automation visits

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

    Ada: Looks like you've been marking items Done manually — you can automate
    that with one rule. Click the ⚡ lightning bolt at the top of this board →
    search "status changes" → pick "When status changes to Done, notify someone"
    and select your team. Takes 30 seconds.
    ```

    *Spots the repetitive behaviour, gives the exact click path, skips the follow-up.*
  </Tab>
</Tabs>

<Tip>
  **How this works** — PostHog or Amplitude sends events to Autoplay's ingest endpoint as users act in your product. There's no stream to subscribe to: your backend pulls a user's recent activity **on demand** — a single REST call to Autoplay's live-activity endpoint, keyed by `product_id` + `user_id` — the moment a user opens Ada. That response is formatted into `metaFields` and passed to the Ada embed before — or during — a chat session. Ada's AI Agent reads these variables inside your bot's Processes to personalise responses.
</Tip>

***

## 📋 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.
* **PostHog or Amplitude already set up** — complete the [Quickstart](/quickstart) first, with `posthog.identify(user.id)` (or `amplitude.setUserId(user.id)`) setting a stable `user_id` on login.
* **Your `product_id` and `mcp_key`** — printed by `onboard_product` in the Quickstart.
* **`autoplay-sdk` installed** — `pip install autoplay-sdk`
* **A backend you can add one route to** — FastAPI is shown below, but any framework that can make an outbound HTTP call works.

<Info>
  **How identity works** — Autoplay keys a user's live activity by the stable `user_id` your activity source identifies them with — the same id you pass to `posthog.identify(user.id)` or `amplitude.setUserId(user.id)`. There's no session id in the read API: every lookup is `GET /users/{product_id}/{user_id}/live-activity`, scoped only by product and user.

  When the user opens Ada, your frontend passes that same `user_id` to your `/context/{user_id}` endpoint (Step 2 below), which fetches their live activity and returns it as `metaFields`.

  If a user is anonymous (not yet identified), your activity source still records events under an anonymous id — Autoplay will have context for that id, but it won't be linked to a real user account until you call `identify` / `setUserId`.
</Info>

<Tabs>
  <Tab title="PostHog">
    ```javascript theme={null}
    posthog.identify(user.id); // same id you must pass to /context/{user_id}
    ```
  </Tab>

  <Tab title="Amplitude">
    ```javascript theme={null}
    amplitude.setUserId(user.id); // same id you must pass to /context/{user_id}
    ```
  </Tab>
</Tabs>

<Note>
  **How the pieces fit:** your app identifies the user in PostHog/Amplitude → Autoplay stores activity under that `user_id` → your frontend passes the same `user_id` to `/context/{user_id}` before opening Ada → the returned `metaFields` are populated with that user's real activity.
</Note>

***

## The building blocks

1. **Define Variables in Ada** — Create the Variables your bot Processes will read. Done once in the Ada dashboard.
2. **Serve context to the Ada SDK** — Expose a lightweight endpoint that pulls a user's live activity from Autoplay on demand (a single REST call — no listener process, no local cache) and passes it as `metaFields`.
3. **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) | Type   | Description                                                   |
| ---------------------------------- | ------ | ------------------------------------------------------------- |
| `user_id`                          | String | Stable id from your activity source — keys the backend lookup |
| `current_page`                     | String | URL the user is on, taken from their most recent action       |
| `recent_actions`                   | String | User's recent in-app actions as a numbered list               |

<Warning>
  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 response and the frontend `metaFields` object.
</Warning>

<Note>
  We dropped `session_summary` from this table. The live-activity endpoint already returns a bounded, recent window (the `limit` query param), so there's no separate summarisation step to run or store. If you want a narrative summary for longer histories, generate it inside your own `/context/{user_id}` handler and add it back as a metaField — it's optional, not required for this integration to work.
</Note>

***

## 🌐 Step 2 — Serve context to the Ada SDK

Your backend exposes one route: `GET /context/{user_id}`. It calls Autoplay's live-activity endpoint synchronously, formats the result, and returns it. No stream to consume, no worker process, no Redis — the call happens the moment your frontend needs it.

### Install dependencies

```bash theme={null}
pip install autoplay-sdk fastapi uvicorn httpx
```

### FastAPI context endpoint

```python theme={null}
# api.py
import httpx
from fastapi import FastAPI

app = FastAPI()

CONNECTOR_URL = "https://mcp.autoplay.ai"   # origin of your mcp_url, without /mcp
PRODUCT_ID    = "your-product-id"           # from onboard_product (Quickstart)
MCP_KEY       = "your-mcp-key"              # from onboard_product (Quickstart)

@app.get("/context/{user_id}")
async def context_for_user(user_id: str):
    async with httpx.AsyncClient() as client:
        res = await client.get(
            f"{CONNECTOR_URL}/users/{PRODUCT_ID}/{user_id}/live-activity",
            params={"limit": 10},
            headers={"Authorization": f"Bearer {MCP_KEY}"},
        )

    if res.status_code != 200:
        return {"user_id": user_id, "current_page": "", "recent_actions": ""}

    actions = res.json().get("actions", [])

    current_page = actions[-1]["canonical_url"] if actions else ""
    recent_actions = "\n".join(
        f"[{i + 1}] {a['title']} — {a['description']}"
        for i, a in enumerate(actions)
    )

    return {
        "user_id":        user_id,
        "current_page":   current_page,
        "recent_actions": recent_actions,
    }
```

<Note>
  Protect this endpoint in production. Validate with a session cookie or short-lived signed token tied to the logged-in user — not the raw `user_id` alone, which would let any caller read any user's context.
</Note>

***

### 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 the user's live activity context first and pass it in on the first open.

```html theme={null}
<!-- 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

```javascript theme={null}
// support.js

// ── User identity ───────────────────────────────────────────────────────
// This MUST be the same stable id you pass to posthog.identify(user.id) or
// amplitude.setUserId(user.id) — not a session id, not an email. Resolve it
// from your own auth/session state.
function getCurrentUserId() {
  return window.currentUser?.id ?? null;
}

// ── 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 userId = getCurrentUserId();

  let meta = {
    user_id:        userId ?? 'anonymous',
    current_page:   window.location.pathname,
    recent_actions: '',
  };

  if (userId) {
    try {
      const res = await fetch(`/context/${userId}`);
      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 your `/context/{user_id}` endpoint builds from the live-activity response (Ada reads this as a Variable):

```
user_id: user_48213
current_page: https://app.example.com/boards/8821034/views/timeline

[1] Page Load: Boards Table View — User landed on the boards table view page
[2] Click Add View — User clicked the "Add view" button on the board toolbar
[3] Click Timeline — User clicked "Timeline" in the Add view panel
[4] Page Load: Boards Timeline View — User landed on the boards timeline view page
[5] Click Set Dates — User clicked "Set dates" button on item "Q2 Feature Launch"
[6] Click Timeline Column — User clicked "Timeline column" on item "Q2 Feature Launch"
[7] Click Automate — User clicked the "⚡ Automate" button on the board toolbar
```

***

## 📣 Useful Ada events to subscribe to

Wire these in `onAdaEmbedLoaded` for guaranteed delivery:

| Event key                  | When                     | Useful for                                                     |
| -------------------------- | ------------------------ | -------------------------------------------------------------- |
| `ada:end_conversation`     | User closes or ends chat | Clear or archive session context on the backend                |
| `ada:agent:joined`         | Live agent joins         | Stop pushing `setMetaFields` updates (human agent has context) |
| `ada:agent:left`           | Live agent leaves        | Resume `setMetaFields` updates                                 |
| `ada:conversation:message` | New message received     | Push a fresh context snapshot via `setMetaFields`              |
| `ada:minimize_chat`        | User minimises chat      | Pause non-critical context updates                             |

***

<Tip>
  **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.
</Tip>
