Skip to main content
Snowplow tells you that something moved in your behavioral data: a step-completion rate slips below trend, an event fires at an implausible rate, a batch of events fails schema validation. It can’t show you what one specific person saw. Subtext can. Attach the Subtext session URL to every Snowplow event as a custom entity and it flows through the pipeline into your warehouse, so any event you model there deep-links back to the matching session — your agent reads the subtext_url, opens that person’s real session, and walks the decisive moments with screenshots, the component tree, network, and console. Snowplow detects; Subtext diagnoses.
This guide assumes the Subtext capture snippet is installed and capturing sessions, and a Snowplow web tracker is running — either the tag (window.snowplow(...)) or the npm @snowplow/browser-tracker. The examples query ClickHouse — which Snowplow feeds via its Snowbridge stream forwarder or via the Lake Loader’s Iceberg/Delta output, not a dedicated loader — but any of Snowplow’s supported loader targets (Snowflake, Databricks, BigQuery, Redshift) works the same way; only the entity-column syntax differs.

Attach the session URL

Snowplow attaches extra data to events as entities (custom contexts), each validated against a schema. The cleanest way to add one value to every event is a global context — define it once and the tracker sends it with every subsequent event, no per-call wiring.

Define the entity schema

Entities are schema-validated, so first add a self-describing schema to your Iglu registry. A minimal one holds a single URL:

Attach it as a dynamic global context

The session URL changes across sessions and carries a per-moment timestamp, so use a context generator (a function) rather than a static object. The tracker runs it for every event, reading the URL fresh, and skips the entity when there is no session yet by returning undefined. Register it once, after the tracker is initialized — every trackPageView, trackSelfDescribingEvent, and built-in event fired afterward carries the entity.

Where it lands in the warehouse

Snowplow shreds each entity into its own column or table named from the schema — for the schema above, a column like contexts_com_yourco_subtext_session_1, an array whose first element holds { "url": "..." }. Query the URL out of that path (ClickHouse example):
The exact column path depends on your loader; check your atomic schema for the shredded entity name.
Don’t read FS('getSession') at module top level — it returns null until the session has started; the generator runs per event, which is exactly when a session exists. Don’t attach the URL as a static global context object, which would freeze the value at registration time — use the generator so each event gets the current, moment-linked URL. Don’t skip the schema step — an entity that fails validation lands in your failed events stream, not your warehouse. And don’t put the signed URL anywhere untrusted readers can reach it; it grants replay access.

From signal to session

However you first hear about a problem in Snowplow — a modeled behavioral table, a Failed Events alert, a data-quality check — the path is the same: get to the subtext_url on the affected rows, then hand it to your agent for a Detect-vs-Diagnose read. The atomic events say a user errored or dropped; the session shows what they actually experienced.
An analytics engineer or PM reviewing a dbt-modeled funnel built on Snowplow atomic events notices a step-completion rate falling below trend for a specific event.Query the modeled table (or atomic.events) for the affected users’ sessions and read subtext_url off the shredded entity column:
Grab a few, then hand them to your agent:
The data engineer who owns tracking quality sees Snowplow’s Failed Events stream spike — events failing schema validation, which usually means a broken client, not just bad instrumentation.Successful events from the same users still carry the entity, so find a recent good event for an affected user and read its subtext_url.
A product analyst investigating a custom event finds a self-describing event firing at an implausible rate or with odd properties.Filter atomic.events to that event schema and pull the session link.
For a funnel investigation across several dropped users, ask your agent to consolidate the findings into one proof document (doc-create) — a screenshot per session and a single shareable link — then drop it into the model’s documentation or the data-quality ticket so the next person sees the human story behind the number.

For agents

An autonomous agent can run the whole loop, but not against Snowplow directly. Snowplow is a pipeline, not a query product: Snowplow’s official MCP server works against the Snowplow Console — tracking plans, pipeline health, and failed-event investigation — not your event data, and querying the collector or enrichment stack is not the path. Snowplow’s value is that it lands clean, modeled events in your warehouse, so the agent story runs through the warehouse’s MCP.
  • If that warehouse is ClickHouse, use the official mcp-clickhouse server (list_databases, list_tables, run_query, read-only by default).
  • For Snowflake, BigQuery, or Redshift, use that warehouse’s own MCP or SQL API. The query shape is identical; only the entity-column syntax differs.
1

Discover and extract in one query

The shredded entity column returns both the friction and the link on the same rows, so a single query does discovery and extraction together:
2

Hand off to Subtext

For each URL, call review-open(session_url=<subtext_url>) on the Subtext MCP and get a Detect-vs-Diagnose read: the model says they dropped versus what the session actually shows.
The entity only round-trips if the tracker attached it at capture (see Attach the session URL); events fired before you registered the global context carry an empty array, so filter length(...) > 0. The shredded column name is derived from your schema’s vendor, name, and version — confirm it against your atomic schema before hard-coding it. Keep the warehouse MCP read-only; a review agent never needs write access.