Skip to main content

⚡ Add this skill

One command

Add the Autoplay Inkeep skill for an existing Inkeep AI support agent setup.
View the docs →

Agent onboarding

Fetch this skill when a customer already uses Inkeep and wants its AI support agent to consume Autoplay live user activity.
View the skill →
This tutorial gets you from zero to a working Inkeep AI chat with grounded context in about 45 minutes. What Inkeep gives you in this integration: Inkeep’s AI chat framework was built for in-product support — grounded in your own product knowledge, not generic LLM replies. The self-hosted agents framework (@inkeep/agents-ui) gives you:
  • introMessage injection — the widget’s opening message is set before the conversation starts. This is the prop this tutorial uses to deliver Autoplay context — so the first reply says “I can see you’ve been on the Connect Data Source step” instead of “How can I help?”
  • context passthrough — structured session data (session ID, user ID, email) flows from the browser into the agent’s system prompt on every turn, scoping replies to the right user
  • Agent / sub-agent routing — a single appId dispatches to different LLM workers by intent. Add a billing sub-agent later without touching the widget code
  • Your LLM key, your data — conversation history, system prompts, and user messages live in a Postgres DB you own; nothing is sent to Inkeep’s cloud
The paid Inkeep CDN widget (@inkeep/cxkit-js) requires an Inkeep cloud API key and calls Inkeep’s hosted endpoints. This tutorial uses the open-source agents framework (@inkeep/agents-ui) which you self-host with your own LLM key. If you already have an Inkeep account, skip Steps 6–7 and point NEXT_PUBLIC_INKEEP_BASE_URL at your cloud endpoint instead.
Before you start, make sure you have:
  • A PostHog account (free tier is fine) — for the click-capture step in Step 1. See PostHog’s own docs if you don’t have one yet.
  • Docker and pnpm — the agents framework runs its backing services (Postgres, Doltgres, SpiceDB) via docker compose, and its dev server via pnpm.
  • An Anthropic or OpenAI API key — for the agents framework’s LLM calls (Step 6).

What you’ll build:
  1. Frontend autocapture with posthog.identify() + posthog.register({email}).
  2. Product onboarding with your PostHog project id for issued Autoplay product_id, ingest, MCP, and owner credentials.
  3. PostHog destination forwarding events to your Autoplay connector.
  4. A small FastAPI bridge that pulls a user’s live activity with a plain httpx call and exposes it over a /context/{user_id} endpoint.
  5. The Inkeep agents framework running locally (Docker + pnpm dev server).
  6. A project, agent, and sub-agent configured via the Inkeep management API.
  7. An InkeepEmbeddedChat widget that pre-loads the pulled Autoplay context as its introMessage.
  8. An end-to-end check where the chat opens knowing exactly what the user was doing.
Runtime loop: click in app → event lands in the Autoplay connector via the PostHog webhook → user opens chat → frontend fetches /context/{user_id} → bridge pulls that user’s live activity over REST, on demand → InkeepEmbeddedChat opens with grounded introMessage.
The connector is pull-based, not push-based. There’s no persistent stream for the bridge to hold open and no local event store to keep in sync — the bridge asks “what has this user been doing?” once, synchronously, exactly when the frontend needs an introMessage. This is the same pattern used in the Plain tutorial and Intercom Fin tutorial — no listener process required.

🪝 Step 1 — Capture clicks in your web app with PostHog

Install posthog-js and initialize it once on app load.
Mount this provider once at the top of your app (app/layout.js in Next.js). Autocapture then sends clicks, page views, and form submits automatically.
Use your Project API Key (starts with phc_). The other keys PostHog surfaces (phx_…) are personal/admin keys and posthog.init() will reject them with a misleading personal_api_key error.
Verify: open the app, click around, then check PostHog → Activity for $autocapture events on your user.

📝 Step 2 — Register your product with Autoplay

Run a one-time script to create your ingest + MCP credentials.
Create bridge/register_product.py:
Run it once:
It prints the registration values — save them:
  • product_id — the issued Autoplay id, e.g. prod_wQ7r8kF9....
  • providerposthog.
  • provider_project_id — your PostHog project id.
  • ingest_url — PostHog will POST events here (e.g. https://connector.autoplay.ai/ingest/prod_wQ7r8kF9...).
  • ingest_secretX-PostHog-Secret header value for the destination.
  • mcp_url — always https://mcp.autoplay.ai/mcp (informational — this integration doesn’t speak MCP, we read the equivalent REST endpoint directly).
  • mcp_key — Bearer token for the REST live-activity read the bridge does in Step 5. Named after the connector’s primary MCP interface, but this integration only ever uses it for plain REST calls.
  • owner_token — shown only on first registration; save it securely for future re-registration or rotation.
contact_email is required. It is stored on the connector product row so Autoplay can reach you. Re-registering the same provider/project pair returns 409 unless you pass owner_token="<saved token>", in which case ingest_secret rotates — update the PostHog destination in Step 3 to match.

🔗 Step 3 — Wire PostHog → Autoplay via a HogQL destination

Configure a PostHog destination to forward each autocapture event to your Autoplay webhook.
  1. PostHog UI → Data pipeline → Destinations → + New destination → HTTP Webhook.
  2. Enable destination = ON.
  3. Webhook URL: paste ingest_url from Step 2.
  4. Method: POST. JSON Body: clear it. Headers: remove the default Content-Type row (the Hog code below sets headers itself).
  5. Click Edit source and paste this script (replace <INGEST_SECRET> and <PRODUCT_ID>).
  1. Click Test function — expect status 200 in under 200 ms.
  2. Create & enable.
PostHog requires the Webhook URL field on the form even though the Hog source above overrides it. Paste the same ingest_url from Step 2 into both places.
Verify: click around your app, then check destination Logs for successful POSTs.

🧰 Step 4 — Scaffold the bridge project

The bridge is the only service that touches the Autoplay SDK. It assembles user context and serves it over a simple HTTP endpoint — Inkeep handles the LLM call. You already created ~/nexus-cloud/bridge/ in Step 2. Add the remaining dependencies:
Why no /reply endpoint, and no LLM key in this bridge? With Inkeep the LLM call happens inside the Inkeep agents framework — your bridge only needs to pull and return the context string, it never talks to an LLM itself. That’s also why the old session-summarizer’s OPENAI_API_KEY is gone: there’s no rolling summary to generate anymore, just a bounded window of recent actions the connector already returns. Your LLM credentials live in exactly one place — the agents framework .env from Step 6.
Create bridge/.env with three of the six credentials returned by onboard_product. Map them as follows:

🔌 Step 5 — Wire the context endpoint

Create bridge/copilot_server.py. There’s no SDK pipeline to compose anymore — the bridge makes one httpx call to the Autoplay connector’s REST live-activity endpoint, synchronously, the moment the frontend asks for context. No listener process, no local event store, no reconnect logic.

5a. Imports and config

5b. The FastAPI app

No lifespan hook is needed — there’s no client to start or stop when the server boots. The app object is just a plain FastAPI().

5c. A helper to pull and format live activity

This is the only piece of “pipeline” left: one function that calls the connector and turns the actions array into a readable block of text. There’s no summarizer step — the connector already returns a bounded, recent window, so raw actions are passed straight through.
actions comes back oldest → newest, so the lines above read as a timeline of what the user just did, in order — the same ordering guarantee the old streamed pipeline gave you, just resolved fresh on every call instead of maintained incrementally.

5d. The /context/{user_id} endpoint and health check

This bridge does not call the LLM for chat replies — Inkeep handles that. It exposes a single read endpoint keyed by the stable user_id (the same id posthog.identify() set) — the frontend fetches it before opening the chat widget to build the introMessage. There’s no session_id anywhere in this API.

Start the bridge

You should see plain uvicorn startup logs — there’s no stream to connect, so no “listening” or “connected” line to wait for:
Smoke-test:
Click around in your app for ~30 seconds, then check the context endpoint:
YOUR_USER_ID is the same id you passed to posthog.identify(...) in Step 1. That confirms events are flowing through PostHog into the connector, and that the bridge’s REST pull is working.

🤖 Step 6 — Run the Inkeep agents framework

Inkeep is an open-source AI agent framework (ELv2 license) that you self-host with your own LLM key. The agents framework ships as a pnpm monorepo.
Start the backing services (PostgreSQL on :5433, Doltgres on :5435, SpiceDB on :50051):
Copy the sample env:
Open .env and set at minimum:
INKEEP_POW_HMAC_SECRET controls browser proof-of-work (ALTCHA). Comment it out for local development. If set, the browser widget must solve a cryptographic challenge before it can open a conversation — this causes a 400 error during testing.
Start the agents API on port 3002:
Health check:

⚙️ Step 7 — Create a project, agent, and sub-agent

The Inkeep agents framework uses a two-layer model: an agent is a named entry point with a routing prompt, a sub-agent is the LLM worker that actually calls the model. Both must exist before InkeepEmbeddedChat can start a conversation. All calls below use the bypass auth header (Authorization: Bearer test-bypass-secret-for-ci), which sets userId='system' and skips permission checks — suitable for local setup only. Create the project:
Create the agent:
Create the sub-agent:
Set the default sub-agent:
Enable anonymous sessions so the browser widget can authenticate without a user login:
Verify the widget can get a session token:
A JWT in the response confirms the widget can authenticate.
Why both an agent and a sub-agent? The top-level agent is the named entry point registered in your app config. The sub-agent is the conversational worker that actually calls the LLM. This separation lets you route different intents to different sub-agents later — for example, a billing sub-agent and a product sub-agent under the same top-level agent — without changing the widget’s appId.

💬 Step 8 — Wire InkeepEmbeddedChat into your app

8a. Configure Next.js

@inkeep/agents-ui ships ESM-only. Tell Next.js to transpile it:
Install the package:

8b. Environment variables

Create frontend/.env.local:

8c. The InkeepWidget component

Create frontend/components/InkeepWidget.tsx. The key pattern here is:
  1. On mount (and whenever contextKey changes), fetch /context/{user_id} from the bridge.
  2. If has_activity is true, build a warm introMessage that leads with what the user was doing.
  3. Pass introMessage to InkeepEmbeddedChat.
  4. Use key={contextKey} to force a full component remount with the new introMessage when the proactive trigger fires (Step 2). Without this prop, React reuses the old component instance and the new intro message is silently ignored.

InkeepEmbeddedChat props reference

Why key and not just updating introMessage? InkeepEmbeddedChat manages its own conversation state internally. Once mounted, it ignores introMessage prop changes — the conversation has already started. Changing key tells React to unmount and remount the component, which creates a fresh anonymous session with the new introMessage as the AI’s opening line. Step 2 relies on this pattern every time a proactive offer fires.

8d. Mount the widget in your page

Add InkeepWidget to your onboarding page. Pass the stable identify id (available from posthog.get_distinct_id()) as userId so the bridge can pull the right user’s activity.
posthog.get_distinct_id() returns the identity posthog.identify(...) set on login — the same stable user_id that flows through PostHog → Autoplay connector. This is the correct join key. There’s no session_id in this API anymore; if you use a custom identity call, make sure the id you pass to InkeepWidget matches the id you pass to posthog.identify().

✅ Step 9 — Try it

  1. Open your app, click through the onboarding wizard for ~30 seconds — e.g. navigate to Step 1 (Connect Data Source), paste a URL into the API endpoint field, click Test Connection.
  2. Click Need help? to open the chat panel.
  3. The widget fetches /context/{user_id}has_activity is true — and mounts with:
    “I can see you’ve been working on the onboarding. What can I help with?”
  4. Ask “my test connection keeps failing” → the agent replies with specific guidance about the Connect Data Source step, grounded in the fact that you just tried it.
The agent should not recite click logs. Activity is a private signal used to warm the introMessage and keep the agent’s reply focused on where you are in the flow. If context is missing, check bridge logs and the troubleshooting matrix.
In Step 2 we’ll go further: the bridge will notice when the user opens the Connect Data Source step three times without completing it, and surface a proactive offer — without the user typing anything.

🛠 Troubleshooting


🔄 Day-2 operations

After editing bridge/copilot_server.py: re-run uvicorn (or start it with --reload during development). After editing the agent system prompt via the API (curl -s -X PATCH …), the change takes effect immediately — no restart needed; the agents framework loads prompt from the database on each conversation turn. To inspect the agent config at any time:
To reset the Inkeep database (wipes all projects, agents, and conversation history):

What you’ve built

You now have an Inkeep AI chat widget whose opening message is grounded in what the user was actually doing — no generic “How can I help?” when you can see they just failed to connect a data source.
  • Reusable bridge: the REST pull pattern is identical to every other support AI agent recipe — swap Inkeep for a different widget later without rewriting context logic.
  • Bring-your-own model: the Inkeep agents framework calls your LLM key directly; no per-seat or per-call fee to Inkeep for the chat itself.
  • Nothing to keep alive: there’s no stream to reconnect, no local event store to keep consistent, no summarizer to tune — the bridge makes one bounded httpx call per context request and returns what comes back.
  • Self-hosted: your events, conversation history, and LLM keys never leave your infrastructure.
If anything in this tutorial wasn’t clear, or you hit a snag the troubleshooting matrix didn’t cover — please reply on the thread or open an issue in the Autoplay SDK repo. Feedback shapes the next version of these docs.