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.
The finished workflow has two parallel paths:
- WebHook → StoreEventsData — every incoming
ActionsPayload is written to AutoPlayEventsTable in near real-time.
- Start → FetchEventsData → Autonomous Agent → End — when someone writes in chat, the agent pulls recent activity plus the running summary, then answers with that context instead of generic replies.
Action note written to AutoPlayEventsTable (near real-time):
title: Click element
description: User clicked element on the pricing page
canonical_url: https://yourapp.com/pricing
user_id: 019dd835-3f68-7e78
session_id: 019dd958-7eea-75a2
Summary note written to AutoPlayEventsSummaryTable after the threshold (default 10 actions):
The user navigated to the pricing page and interacted with several
diffusion model options, suggesting they are configuring an image
generation workflow.
End-to-end walkthrough
🔗 Step 1 — Install the Webhook integration in Botpress
Open your bot in Botpress Studio, go to Integration Hub, search for Webhook, and install it.
Copy the generated Webhook URL shown on the configuration page and save it — you will need it in Step 4 as BOTPRESS_WEBHOOK_URL.
🗄️ Step 2 — Create the tables for storing events data
Create the Botpress Tables before you add workflow code nodes that reference them.
In Botpress Studio, open Tables and create two new tables.
AutoPlayEventsTable
Stores individual user actions as they arrive from the stream.
| Column Name | Data Type |
|---|
title | String |
description | String |
canonical_url | String |
user_id | String |
product_id | String |
session_id | String |
AutoPlayEventsSummaryTable
Stores the rolling summary generated by AsyncSessionSummarizer. A single row (id = 1) is upserted on each summarisation cycle.
| Column Name | Data Type |
|---|
summary | String |
session_id | String |
⚙️ Step 3 — Create the workflow
In Botpress Studio, create a new workflow with the following four nodes:
| Node | Type | Purpose |
|---|
FetchEventsData | Code | Query recent events + summary from Tables |
Autonomous Agent | AI Agent | Answer user questions from the fetched context |
WebHook | Webhook trigger | Receive ActionsPayload from your Python script |
StoreEventsData | Code | Write incoming actions to AutoPlayEventsTable |
Connect them as shown: Start → FetchEventsData → Autonomous Agent → End and (in parallel) WebHook → StoreEventsData → End.
Code node: FetchEventsData
Paste this into the FetchEventsData code node. It reads the current summary record and all events created in the last 2 minutes, then exposes the result as workflow.agentContext.
const filter = { id: 1 } // filter for the single summary row
const summaryRecord = await AutoPlayEventsSummaryTable.findRecords({ filter })
console.log('summary', summaryRecord)
const summary = summaryRecord[0].summary
// Events from the last 2 minutes
const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000).toISOString()
const recentEvents = await AutoPlayEventsTable.findRecords({
filter: { createdAt: { $gte: twoMinutesAgo } }
})
console.log('recentEvents', JSON.stringify(recentEvents))
const result = {
recentEvents,
summary: summary && summary.length > 0 ? summary[0] : null
}
console.log('result', result)
workflow.agentContext = result
Code node: StoreEventsData
Paste this into the StoreEventsData code node. It maps each action in the incoming webhook payload to a table row and saves them all in one call.
const body = event.payload.body
console.log('jsonBody:', JSON.stringify(body))
const records = body.actions.map((action) => ({
title: action.title,
description: action.description,
canonical_url: action.canonical_url,
user_id: body.user_id,
product_id: body.product_id,
session_id: body.session_id,
}))
await AutoPlayEventsTable.createRecords(records)
console.log('Records created:', records.length)
Autonomous Agent system prompt
Before writing the system prompt, create a workflow variable called agentContext (Scope: Workflow, Type: Object).
Then open the Agent instructions panel and paste the prompt below. Remove the placeholder line, place your cursor there, and click the agentContext variable to inject it inline.
You are a proactive software adoption assistant. Your role is to help users
succeed with the product by understanding exactly where they are in their
journey — based on their live session activity and a rolling summary of
their recent behaviour.
You have access to two data sources:
- **recentEvents**: A list of actions the user has taken in the last 2 minutes
(page visits, clicks, feature interactions, form submissions, etc.)
- **summary**: A rolling narrative of what the user has been doing this session
(may be null if the session is new)
Here is the user's current session context:
<PLACEHOLDER: PLACE MOUSE CURSOR HERE>
When responding:
- Identify which part of the product the user is currently working in, and
what they appear to be trying to accomplish
- Spot signs of confusion or friction (e.g. repeated visits to the same page,
abandoning a flow, clicking around without completing an action)
- Offer specific, actionable guidance that meets them exactly where they are —
not generic help documentation
- If they are stuck, proactively suggest the next step or the feature that
would unblock them
- Keep responses concise and practical — users are mid-task, not reading docs
- If no session data is available, ask a clarifying question to understand
what they are trying to do
Your north star is reducing time-to-value: every response should move the
user one step closer to completing their goal inside the product.
🐍 Step 4 — Set up the webhook for streaming events to Botpress
This Python script connects to your Autoplay stream, forwards raw action payloads to the Botpress Webhook, and periodically upserts a summary into AutoPlayEventsSummaryTable.
Botpress credentials
You need two values from Botpress:
- Access Token — app.botpress.cloud → Account Settings → Access Tokens
- Bot ID — extracted from the Studio URL:
https://studio.botpress.cloud/<bot-id>/flows/...
Integrate AsyncAgentContextWriter
import asyncio
from autoplay_sdk import (
ActionsPayload,
AsyncAgentContextWriter,
AsyncConnectorClient,
AsyncSessionSummarizer,
)
import httpx
import requests
import json
import openai
# ── Autoplay ──────────────────────────────────────────────────────────────
STREAM_URL = "https://your-connector.onrender.com/stream/<your-product-id>"
UNKEY_API_KEY = "your-unkey-api-key"
# ── Botpress ──────────────────────────────────────────────────────────────
BOTPRESS_ACCESS_TOKEN = "your-botpress-access-token"
BOT_ID = "your-bot-id"
BOTPRESS_WEBHOOK_URL = "your-botpress-webhook-endpoint-url"
botpress_client = httpx.AsyncClient(
base_url="https://api.botpress.cloud/v1/tables",
headers={
"Authorization": f"Bearer {BOTPRESS_ACCESS_TOKEN}",
"Accept": "application/json",
"x-bot-id": BOT_ID,
},
)
# ── OpenAI (used by the summarizer) ───────────────────────────────────────
async_openai = openai.AsyncOpenAI()
def api_call(func):
async def wrapper(*args, **kwargs):
try:
return await func(*args, **kwargs)
except Exception as e:
print(f"Error in API call: {e}")
return wrapper
@api_call
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
Forward actions to the Botpress Webhook
Because we post the raw JSON payload ourselves, we pass a no-op dummy_write_actions into AsyncAgentContextWriter so the SDK does not try to write actions a second time.
@api_call
async def post_actions_to_webhook(payload: ActionsPayload) -> None:
print("\n=== POSTING ACTIONS TO WEBHOOK ===")
data = {
"session_id": payload.session_id,
"user_id": payload.user_id,
"product_id": payload.product_id,
"actions": [
{
"title": action.title,
"description": action.description,
"canonical_url": action.canonical_url,
}
for action in payload.actions
],
}
response = requests.post(
BOTPRESS_WEBHOOK_URL,
data=json.dumps(data),
headers={"Content-Type": "application/json"},
)
print(f"Webhook response status: {response.status_code}")
async def dummy_write_actions(session_id: str, text: str) -> None:
pass # raw payload is already sent via post_actions_to_webhook
@api_call
async def overwrite_summary(session_id: str, summary: str) -> None:
print("\n=== OVERWRITING SUMMARY IN BOTPRESS TABLE ===")
payload = {
"rows": [{"id": 1, "session_id": session_id, "summary": summary}],
"keyColumn": "id",
"waitComputed": True,
}
response = await botpress_client.post(
"/AutoPlayEventsSummaryTable/rows/upsert",
json=payload,
headers={
"x-bot-id": BOT_ID,
"Authorization": f"Bearer {BOTPRESS_ACCESS_TOKEN}",
"Content-Type": "application/json",
},
)
print(f"Summary upsert response status: {response.status_code}")
Wire AsyncAgentContextWriter
summarizer = AsyncSessionSummarizer(llm=llm, threshold=10)
agent_writer = AsyncAgentContextWriter(
summarizer=summarizer,
write_actions=dummy_write_actions,
overwrite_with_summary=overwrite_summary,
debounce_ms=0,
)
async def handle_actions_interceptor(payload: ActionsPayload):
# 1. Send the raw structured JSON to the Botpress Webhook
await post_actions_to_webhook(payload)
# 2. Forward to the agent_writer so it counts toward the summarizer threshold
await agent_writer.add(payload)
Connect to the stream
async def main():
async with AsyncConnectorClient(url=STREAM_URL, token=UNKEY_API_KEY) as client:
client.on_actions(handle_actions_interceptor)
print("Listening for live website clicks… (Press Ctrl+C to stop)")
await client.run()
asyncio.run(main())
That’s it. Events will start appearing in AutoPlayEventsTable as soon as the user interacts with your UI. Summaries are generated and upserted automatically once the action threshold is reached.
Next: Step 2 — Define proactive triggers