Review session recordings with evidence-based findings
Agent skill that turns Microsoft Clarity session recordings into verified findings via triage, attribution, replay watching and database triangulation.
1.0.0Add to Favorites
Why it matters
Transform Microsoft Clarity session replay analysis from anecdotal storytelling into a rigorous, evidence-backed investigation process that triangulates replay data with database records and source code to produce verifiable findings instead of confident-but-wrong conclusions.
Outcomes
What it gets done
Attribute anonymous Clarity sessions to real users by joining session-entry URLs with your product database
Triangulate replay observations against database state and frontend source code to verify every load-bearing claim
Run periodic session-intelligence reports across all recordings using a resumable multi-agent workflow with triage, watch, and adversarial refutation phases
Conduct targeted investigations with dead-click analysis, thin-session forensics, and UI-clarity reviews that ship with evidence coordinates for two-minute verification
Install
Add it to your toolbox
Free account needed to copy or download. It lets your agents use Spark over MCP and report back whether an asset worked.
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/bayramannakov-clarity-session-review | bash After your agent runs this, report what happened — the next agent that picks it sees your result before they choose.
Reports
Agent outcome reports
No reports yet
Overview
clarity-session-review is a reusable agent skill that supplies a judgment layer on top of Microsoft Clarity's own tools (MCP, API, dashboard). The method runs question-scoped triage, attribution, replay watching, database and source triangulation and a verifiable evidence appendix, and it also reads as a plain playbook for a human. Use it for a targeted question such as why a user churned or whether a fix worked, or for a recurring review of every session in a window. It is not an API wrapper, a scraper or a substitute for aggregate analytics.
What it does
clarity-session-review is a reusable agent skill for reviewing Microsoft Clarity session recordings in a way that produces findings, not anecdotes. It runs as a Claude Code or Codex skill, a SKILL.md the agent loads on demand, and the method also reads as a complete manual playbook for a human. It covers question-scoped triage, attribution, replay watching, database and source triangulation, and a verifiable evidence appendix.
Eight principles shape it, each tied to a specific past mistake:
- Question before player: with no discriminating question, no watching.
- Attribute before interpreting: wire Clarity's Identify API if possible, otherwise use session-entry URLs to join sessions to your database.
- A replay alone is a story; replay plus database plus source code is a finding.
- Timeline is not replay: API timelines are truncated and masked, so treat their counts as floors and never present a timeline read as watched.
- Every claim ships with evidence coordinates: link, timestamps and what to check.
- Count people, not sessions: Clarity fragments one visit into many sessions (one user showed up as 59), and one run's 462 sessions were about 26 real people.
- Audit what the UI shows, not only what users click (provisional).
- Walk the step before explaining it: a partial walk is worse than none, and if a step cannot be finished it goes to a human for full-path screenshots.
The staged method escalates only when a cheaper layer cannot answer. Stage 1 pulls timelines and starts from the Clarity MCP query-analytics-dashboard tool for untruncated aggregates such as dead-click and rage percentages, top pages, referrers and JavaScript errors. Hygiene includes excluding team sessions by browser uid, subtracting hidden-tab time from durations, and collapsing a burst of same-uid single-page rows into one visit. Stage 2 harvests the complete events panel in one JavaScript pass and takes targeted stills. Stage 3 is a window-scoped pointer-trace poller with a validation gate. Stage 4 triangulates against the product database, frontend source, URL identifiers used as join keys and referrer chains. Stage 4.5 walks the step yourself, and Stage 5 hands intent and emotion questions to a human with a player link, the question and a timestamp window.
Every watched session reports MEASURED (events or database), OBSERVED (frames or trace, described not interpreted) and INFERRED (motive, labeled) content, and records the device, because a phone session and a desktop session are different products.
A session's information sits in three layers with different access costs: events (pages, clicks with element text, dead and rage clicks, text entry, Page hidden and visible, durations, from the API or MCP timeline, per the timeline-is-not-replay principle above), UI states (what the user saw at a moment, from a player frame at a timestamp) and dynamics (cursor trajectory, hover-linger, hesitation and scroll rhythm, from the replay stream). Favorite everything relevant early, because Clarity's rolling retention is 30 days and favorites last about 9 months. The player's own Insights tab and Copilot summary give hypotheses, never evidence.
Player mechanics matter for stills: wait about 8-9 seconds after navigation, confirm the clock because autoplay state is ambiguous, seek by clicking the timeline bar and then verify the on-screen clock (the plus or minus 10 second buttons are flaky), and close the "Recording ended" dialog with its X. If the events harvest returns an empty array on a session that visibly has events, the harvester is broken until proven otherwise: the labels changed (a Clarity release or a non-English locale) or the panel DOM was restructured. The pointer-trace poller reads the same-origin replay iframe, where the virtual cursor is the .clarity-pointer-move node and clicks are .clarity-click nodes. It polls about every 150 ms with skip-inactivity off, timestamps against the on-screen player clock, and no trace claim beats INFERRED until known API clicks appear at the right time on the right element, 1x and 2x runs agree, skip on and off reconcile and input density is probed.
When to use - and when NOT to
Use it in two modes. Targeted investigation answers one question, such as why user X churned, whether a fix worked, or what a user did before a call. Periodic census is a recurring review over every session in a window: census 100 percent, replay-watch the flagged set, triangulate every finding. On a first run of about 460 sessions the flagged set was roughly 40. At thousands of sessions per week, census through the aggregate dashboard and sample the watch population by funnel stage or entry surface.
It is the judgment layer on top of Clarity's own tools. It is not a Clarity API wrapper or scraper, and it does not replace analytics: it explains what aggregate numbers cannot and names what the instrument is blind to.
Watching every session is not the goal. Full unscoped 1x watches are for weekly calibration (about 1 per cohort), sessions under 3-4 minutes of activity, sessions where the stages contradict, or sessions that feed a load-bearing decision.
Inputs and outputs
Inputs are Clarity recordings and timelines, your product database, your exclusion list and the frontend source. The output is a report with a mandatory dead-click section and an evidence appendix, one row per session: who, player link, moments to seek to, the claim, what a verifier should check and a REPLAY or TIMELINE depth label.
Standing modules:
- Clarity instruments: aggregate frustration metrics first, dead-click ranking by top pages, quick backs, JavaScript and click errors checked every run, heatmaps and funnels. Scroll and attention maps measure document scroll, so they are blind to nested scroll containers.
- Thin-session forensics: strip bots first. Their signature is 2 seconds or less, 0 clicks, 1 page, entry URL equal to exit URL, and a new browser uid every time. Thin sessions (under 30 seconds and at most one click) carried 50-60 percent of sessions in the runs behind the skill.
- UI-clarity review: a source lint for labels mapped to more than one state, user states without a next action, meaning lost without colour and tooltip, and affordances that depend only on hover or focus, which do not exist on touch. Clarity has no hover heatmap.
- Dead-click analysis: a Dead click label fires for copy-select drag-starts, reading-taps and genuine false affordances, so a count is not a defect count. Only a repeated click on a control-styled target in an action context survives as a defect.
- Cross-instrument and rate gates: compare the same unit, window and population filter, and spot-check by name that the second instrument can even see the people the first one found.
Further Clarity instruments: heatmap types (Click with All, Dead, Rage, Error, First and Last clicks, plus Scroll, Attention, Area and Conversion) are dashboard-only, and because product URLs are per-account the matcher should be contains on a path segment such as a chat route to pool the whole working surface. A scroll map can return a decisive negative, for example a fixed app shell that does not page-scroll, which rules out a fold hypothesis. Heatmaps overlay pooled data on one captured page, often an internal session, and the device type defaults to desktop. Funnels, the Inactive pages insight, Smart events and user intent, and filters (JavaScript error, rage click, device, country, referrer, UTM) complete the set, and naming which instruments ran matters because recordings are one of about ten families.
Thin-session forensics reads the residue: OAuth bounce-backs (referrer is the hosted-auth domain), parked-tab heartbeats (thin sessions entering directly on a working-surface URL), auth loops (a uid with 3 or more sign-in-entry sessions and no workspace), an acquisition census by entry and referrer, email-click forensics (utm_ separates human clicks from scanner hits) and competitor recon. Output goes to data/thin_forensics.json. UI-clarity review adds a cold-read probe, where two or three context-free agents say what each label means and the answers are diffed against source truth, and a frame review for control-styled elements in action contexts. A UI-clarity finding is reportable only with a prevalence number from the production database and a stated cost. Dead-click analysis classifies each target into six classes (whole-surface false affordance, pseudo-interactive text, disabled controls during async work, status chips styled as actions, translate or locale artifacts, rage-adjacent repeats) with a fix direction, and reads the dead-click percentage monthly as a UX-debt KPI, with a check two weeks after each new surface ships. Every claim class needs a verification row: required raw evidence, known false-positive modes and alternative explanations that must die.
Before a rate ships: check the denominator is people, that the affordance existed for the whole window (run MIN(created_at) on the table for the next step and git log -S on the control), that anyone attempted the step, and that the window was chosen before seeing the data. Four discovery modules balance the failure-focused questions: first-session comprehension (an experiment that outputs a behavioral divergence point, at least two labeled explanations and a probe, never a motive), input-struggle and abandoned intent, person-arc and return-visit, and success-session shape.
Dead-click fix directions are whole-surface-clickable, add-affordance, make-inert, link-it and instrument. For a card or tile whose whole container reads as clickable but only a small inner call to action works, the fix is to make the entire surface clickable, the opposite of making it inert. Each top target is triangulated against the frontend source (does the element have an onClick, is it wrapped in a link or button, or is it inert markup, citing file and line) and against the product database. The source lint for UI clarity is mechanical and proven, while the cold-read and frame parts are still maturing, so items from them should not be presented at the same confidence. If the pointer-trace filmstrip fallback is needed, record with screencapture -v or ffmpeg at 1x, because gif_creator captures almost nothing during passive playback. Timelines are compacted to relative paths before agents see them because full URLs get blocked by the harness's output filter.
Integrations
The skill works with Clarity's MCP, API and dashboard, a product database for outcomes, and frontend source for UI states. Periodic mode runs as a resumable multi-agent workflow: a scout pulls through the API or MCP in day-slices, enriches every workspace with your database and excludes at uid level; parallel triage works in batches of about 25 sessions, classifying with schema-forced output against a known-actors list; serial watch uses one browser and one watcher at a time with a fresh context per session, capping the watch list at about 2 per workspace with exemptions for first sessions of new humans and first-week accounts; and parallel synthesis adds adversarial refutation. The Clarity API caps count at 250 per pull, so longer windows use day-slices, and it returns 503 under load, so back off about 90 seconds.
Install by cloning and symlinking into the skills directory:
git clone https://github.com/BayramAnnakov/clarity-session-review
cd clarity-session-review
ln -s "$PWD" ~/.claude/skills/clarity-session-review
Then adapt it to your product with the eight-item checklist covering the URL-to-identity join, your database, your exclusion list and your source paths. It auto-loads when you ask to watch recordings, check Clarity, review all sessions, find why users dropped off, verify a fix or run the dead-click review. It is MIT licensed.
Source README
clarity-session-review
A reusable agent skill for reviewing Microsoft Clarity session recordings the way that produces findings, not anecdotes - and doesn't let a half-trusted event timeline become a confident, wrong story.
It's a discipline, not a dashboard: question-scoped triage → attribution → replay-watch
→ database/source triangulation → a verifiable evidence appendix. It runs as a
Claude Code / Codex skill (a SKILL.md the agent
loads on demand), but the method reads fine as a plain playbook for a human, too.
Why it exists
It was born the day replay-watching overturned four timeline-based conclusions in a single investigation:
- a user filed as "bailed at the redirect" had actually spent two minutes inside the hosted OAuth flow;
- a "five-day starer" was a hidden background tab;
- a "never-returned" user had returned;
- a dramatic "48-minute struggle session" was an internal teammate's own browser.
Every one was read straight off the Clarity event timeline. Watching the replays - and checking them against the product database and the frontend source - reversed all four. This skill is the checklist that makes that the default, not the lucky exception.
The eight principles (each one paid for by a specific mistake)
- Question before player. No discriminating question → no watching.
- Attribute before interpreting. Wire Clarity's Identify API if you can - it's the first-class join. Until then, session-entry URLs usually carry the identity - join it to your database before you interpret anything.
- A replay alone is a story; replay + database + source code is a finding. Triangulate every load-bearing claim.
- TIMELINE ≠ REPLAY. API timelines are truncated and masked - treat their counts as floors; never present a timeline read as watched.
- Every claim ships with its evidence coordinates - link + timestamps + what to check. Verification should be a two-minute job.
- Count people, not sessions. Clarity fragments one visit into many "sessions" (one user showed up as 59); on our runs 50-60% of a pull was thin bounces. One run's 462 sessions was ~26 real people - lead with that number.
- Audit what the UI shows, not only what users click. The method is behavioral and blind to a misleading label or a status with no next action; a source-lint of the label code catches those. (Provisional.)
- Walk the step before you explain it. Before theorizing about any step, execute it yourself on the real product, end to end. Replays tell you WHERE people stop; only walking tells you WHAT you were asking of them. A partial walk is worse than none - it manufactures confidence: one halted at a credential field and published a careful account of the ask, missing that the real ask was a password and a live 2FA code. If you can't finish a step (credentials, payment, a third-party account), hand it to a human for full-path screenshots - never infer a flow's tail.
What's inside
| File | What it is |
|---|---|
SKILL.md |
The skill: the eight principles, the two modes, the pipeline, the report contract, the replay-semantics gotcha list, the cadences, and the blind spots. |
references/method.md |
Per-session mechanics + the standing modules: the Clarity-instruments toolkit (recordings are 1 of ~10 families), thin-session forensics, dead-click analysis (with a verify-first gate - a dead-click count is not a defect count), the UI-clarity review, the discovery modules (first-session comprehension · input-struggle/abandoned-intent · person-arc/return-visit · success-session shape), and a date-stamped Clarity-facts appendix. |
references/periodic-review.md |
Mode B - the recurring "session-intelligence report" over ALL sessions in a window, as a resumable multi-agent workflow (scout → triage fan-out → serial watch → synthesize + adversarial refute). |
ADAPTING.md |
The eight-item checklist to wire the method to your product (URL→identity join, your database, your exclusion list, your source paths…). |
examples/ |
A templatized, data-driven report generator. |
Two modes
- Targeted investigation - a specific question ("why did user X churn", "verify the fix", "what did this user do before the call"). Run the pipeline directly.
- Periodic census - the recurring report over every session in a window. Same principles, orchestrated as a resumable multi-agent workflow.
Install (as an agent skill)
Clone, then symlink (so updates propagate) or copy into your agent's skills directory:
git clone https://github.com/BayramAnnakov/clarity-session-review
cd clarity-session-review
# Claude Code (global skills)
ln -s "$PWD" ~/.claude/skills/clarity-session-review
Then adapt it to your product with the ADAPTING.md checklist. The
skill auto-loads when you ask your agent to "watch recordings", "check Clarity", "review
all sessions", "why did users drop off", "verify the fix", or "run the dead-click review".
Not using an agent? Read SKILL.md and the references/ top-to-bottom - it's a
complete manual method.
What this is not
- Not a Clarity API wrapper or a scraper - it's the judgment layer on top of Clarity's own tools (MCP, API, dashboard).
- Not a substitute for your analytics - it's the replay discipline that explains what the aggregate numbers can't, and names what the instrument is blind to.
Credit & license
Distilled from real practice by Bayram Annakov. This is a public snapshot of a living internal skill - the principles are battle-tested; the examples are anonymized. Contributions and adaptations welcome.
MIT - see LICENSE.
references/method.md
How to watch a session recording (staged method)
Canonical home of the method. The unit of watching is never "a recording" - it is a QUESTION scoped to a TIME-WINDOW. A session's information lives in three layers with different access costs; escalate only when the cheaper layer can't answer.
| Layer | What it holds | Where it lives | Access cost |
|---|---|---|---|
| 1 - Events | pages, clicks + element text, dead/rage clicks, text-entry, Page hidden/visible, durations | API/MCP timeline - may be TRUNCATED (endpoint/version-dependent; see Clarity-facts appendix): aggregates are floors; the player events panel is the complete log | seconds per 100 sessions |
| 2 - UI states | what the user SAW at moment X (modal copy, agent message, errors, banner) | player frame at a timestamp; full events panel | ~2 actions per still |
| 3 - Dynamics | cursor trajectory, hover-linger, hesitation-retreat, scroll rhythm | the replay stream - extractable as DATA via the pointer-trace poller | ~1-2 min per window |
Stage 0 - the question, or don't watch
Write the discriminating question BEFORE opening the player. No question → no watching.
Stage 1 - structured triage (API/MCP, all sessions)
Pull timelines; classify every session; most close here. Cheap aggregate layer
first: the Clarity MCP query-analytics-dashboard tool returns untruncated
aggregates (dead-click %, rage %, top pages, referrers, geo, JS errors) - get the
census shape from it before touching recordings. Hygiene:
- Attribute first: browser-uid across unrelated workspaces = team session → exclude (uid-level: exclude that uid everywhere). Verify a sign-in flow for "new user" claims. Dedup cross-account humans. Collapse Clarity fragmentation - a burst of same-uid single-page rows in one minute is ONE visit, not N sessions.
- Duration discipline: subtract hidden-tab time (totalDuration AND activeDuration include it).
- Truncation: API timelines may be truncated (endpoint/version-dependent) - treat event counts as floors, and spot-check one dense session's API timeline against its player events panel before relying on API counts for anything load-bearing.
- Favorite everything relevant today (30-day rolling retention; favorites are kept ~9 months, not forever).
- Output per surviving session: hypothesis + exact window(s) where the answer lives.
Stage 1.5 - the player's own AI (free hypotheses)
Per-session Insights tab + list-level Copilot summary. Hypotheses, never evidence.
Stage 2 - full events + targeted stills (layer-2 questions)
Open the player once per flagged session:
(a) Harvest the complete events panel in ONE JavaScript pass - do not
scroll-screenshot it. In the player tab (javascript_tool), collect the event rows
by innerText pattern:
let results = [];
document.querySelectorAll('div, li').forEach(el => {
const t = (el.innerText || '').trim();
// keep only the OUTERMOST element of a nested identical chain (one per rendered row)
const p = el.parentElement;
if (p && (p.innerText || '').trim() === t) return;
if (/^(Click|Page hidden|Page visible|Dead click|Selected text|Resized page|Rage click|Text input|Quick back click|Smart event|Entered text)[\s\S]{0,120}\d{2}:\d{2}$/.test(t)
&& el.children.length <= 6 && !t.includes('\n\n')) {
results.push(t.replace(/\n+/g, ' ')
.replace(/https?:\S+/g, '<url>') // strip URLs or output gets blocked
.replace(/[?&]\S+/g, ''));
}
});
// remaining identical rows are REPEATED EVENTS (a hammered dead click) — evidence,
// not noise: keep them with counts instead of Set-deduping them away
const rows = results.filter(r => (r.match(/\d{2}:\d{2}/g)||[]).length === 1);
const counts = new Map();
rows.forEach(r => counts.set(r, (counts.get(r) || 0) + 1));
[...counts].map(([r, n]) => n > 1 ? `${r} ×${n}` : r);
Slice the array if it exceeds output limits. This recovers everything the API truncated (Smart events, Entered text, Quick back clicks included).
If the harvest returns [] on a session that visibly has events, diagnose before
trusting it as "quiet": count elements matching only the trailing-timestamp anchor
([...document.querySelectorAll('div,li')].filter(el => /\d{2}:\d{2}$/.test((el.innerText||'').trim())).length).
Nonzero ⇒ the event LABELS changed (a Clarity release, or a non-English UI locale -
the alternation above is English-fitted); zero ⇒ the panel DOM restructured
(re-derive the selector). el.children.length <= 6 is version-fitted, not a player
invariant. An empty harvest is a broken harvester until proven otherwise.
(b) Targeted stills: seek to each decisive window and screenshot the frame. Player mechanics (all verified the hard way):
- Wait ~8-9s after navigation for the player to load; autoplay state is ambiguous - your first play/pause click may pause it. Screenshot to confirm the clock.
- Seek by clicking the timeline bar, then VERIFY the on-screen clock and nudge - pixel math is unreliable (markers, margins). The ±10s buttons are flaky; prefer timeline clicks.
- The "Recording ended" dialog blocks everything - close via its X.
- Playing through a window: press play,
waitN seconds, screenshot - repeat. For a 10-60s decisive window this beats frame-hunting.
Stage 3 - pointer-trace poller (layer-3 dynamics, window-scoped)
The replay iframe is same-origin; the virtual cursor is a DOM node
(.clarity-pointer-move), clicks are .clarity-click/.clarity-click-ring nodes.
- Skip-inactivity OFF for dynamics (a user motionless READING is the signal); 1× for measurement.
- Poll ~150ms: cursor left/top +
elementsFromPoint→ first element notclarity-*and not CANVAS → record{playerClock, x, y, element}deduped. - Timestamp against the on-screen PLAYER CLOCK (immune to speed/skip).
- Harvest as dwell segments (linger / trajectory / approach-then-retreat).
- Validation gate before any trace claim beats INFERRED: (a) known API clicks appear at the right time on the right element; (b) 1× vs 2× agree; (c) skip on/off reconcile; (d) probe input density (tween-interpolation means sub-second structure is synthetic). Publish effective resolution; refuse claims below it.
- Filmstrip fallback:
screencapture -v/ffmpeg at 1× (NOT gif_creator - it captures ~nothing during passive playback); record once, re-extract frames at higher fps.
Stage 3.5 - full-watch calibration
Watch UNSCOPED at 1× when: weekly calibration (~1/cohort), session <3-4 min active, stages contradict, or the session feeds a load-bearing decision. Log what full watching catches that the pipeline missed.
Stage 4 - triangulate before it becomes a finding
- Your product database is ground truth for outcomes - the tables that record the real OUTCOME: a watched OAuth attempt vs the connect-action rows (created only on the provider's success webhook); a banner on screen vs the subscription-state / quota columns; "never used again" vs the conversation-message census; identity vs the user names/emails.
- Source code explains surprising UI states: grep the frontend/backend for the copy string (e.g. a banner's render condition, an email's eligibility logic) - replay + DB + source together turned "weird banner" into a source-confirmed copy bug with a measured 0/5 conversion baseline.
- URL identifiers are join keys: your product's session-entry URLs (e.g.
app.example.com/{accountId}/{objectType}/{objectId}) - the object UUID resolves the exact record in your database (what was asked, what the agent replied) and separates users sharing an account. - Referrer chains stitch sessions into stories: a session starting with
referrer=<your OAuth provider's hosted-auth domain>is a bounce-back from hosted auth; hour-apart sessions can be one user-day. - Enrichment-column anomalies are findings - scan the joined account-info for suspicious uniformity, not just per-session behavior: a few thousand accounts sharing one subscription-expiry date (a migration artifact stamping legacy free accounts with one expiry) surfaced from the enrichment data, on no recording at all.
- Cross-instrument timestamps: align player-relative, API wall-clock, and DB UTC before claiming sequence.
Stage 4.5 - walk the step yourself (SKILL.md principle 8)
Before any finding about why a step leaks, go do that step on the real product, end to end, and screenshot every screen the user actually meets. Replays show WHERE people stop; only walking shows WHAT you were asking of them, and this method - being about watching others - will never prompt it. The cost of skipping it: a full investigation (every replay, DB censuses, source, an expert panel) that landed on the wrong mechanism for a step sitting six clicks away on production that no one had clicked.
- Finish the flow, or claim nothing past where you stopped. Partial walks manufacture false confidence: a walk that halted at a third-party credential field published "what we ask users to do" and missed that the true ask was password plus a live 2FA code - a materially harder request, one screen further on.
- When you cannot or should not complete it (real credentials, a payment, a genuine third-party account - the security boundary is real, do not cross it): STOP and hand it to a human. Get their screenshots of the whole path, including the success state. Never infer the tail of a flow you did not see - the tail is usually where the ask gets heavy.
- The screenshots are evidence: they belong in the appendix beside the replays, dated, with the exact click-path to reproduce them.
Stage 5 - human watch
Only for intent/emotion judgment calls that survive Stage 4. Hand over: player link + the question + the timestamp window - never "watch this recording".
Output contract (every watched session)
MEASURED (events/DB) / OBSERVED (frames or trace - describe, don't interpret) /
INFERRED (motive, labeled) + the Stage-0 answer. Every OBSERVED line carries
provenance: [stage | instrument | speed | skip on/off | resolution | window covered].
Record the DEVICE for every watched session - it is an attribution field, not metadata. The player's Info tab gives device · OS · viewport · location · Clarity's own "User intent" score. Read it BEFORE interpreting behaviour: a 440×796 MobileSafari session and a 1536×742 desktop session are different products. (Paid for: three "silent" signups read as low-intent tyre-kickers until the Info tab showed all three were on phones - one of them inside the Google App's in-app webview - and the source then proved the welcome card's only handlers were hover-based, i.e. dead on touch. Device turned an anecdote into a defect class.) Negative evidence states coverage + resolution. Per session, one appendix row: who (workspace + user) · player link · the moments to seek to · the claim · what a verifier should check · depth label (REPLAY/TIMELINE) - the evidence-appendix pattern (one file a verifier can walk link by link). Contradictions update the upstream census.
Clarity instruments - use the whole toolkit, not just recordings (standing module)
We default to session recordings + per-session events and leave ~80% of Clarity
unused. Pull these via the query-analytics-dashboard MCP tool (natural-language,
give an explicit date range) or the dashboard - most need NO watching and form the
census + issue-discovery layer. What each finds (30-day window, verified):
- Aggregate frustration metrics - get these FIRST as the census layer. One query returns dead clicks / rage clicks / quick backs / excessive scrolling totals (e.g. 523 / 10 / 153 / 0). Untruncated and cross-session - unlike per-session floors. Exclusions apply - team/test/internal accounts appear in the totals; strip them.
- Dead-click ranking = the aggregate metric, NOT per-session floors. "Top pages by dead click count" is the correct ranking layer for the dead-click module (the run used per-session floors of 164; the aggregate was 523). Then per-target verify (copy-select / reading-tap / false-affordance) - the count stays an upper bound, and it's dominated by copy-out users (one heavy user's 77 were mostly text-selection).
- Quick backs (NEW - was unused). A click out to an external site, user bounces
straight back. High on a working page = users round-tripping to an external site
(e.g. LinkedIn) to vet records → they want in-app detail (e.g. ~52 of 153 on one
active user's working pages). High on
/sign-in= auth-flow friction / OAuth bounce (e.g. 23). - JavaScript errors + click errors - check EVERY run. A silent-breakage guard the behavioral method cannot see; state the result even when clean (e.g. 0 - a clean bill). "Click errors" (JS error right after a click) auto-tags the session - filter to it.
- Heatmaps (validated - visual, dashboard only, NOT the NL API). Open
Heatmaps → pick map type (Click has a sub-menu: All / Dead / Rage / Error / First /
Last clicks · plus Scroll · Attention · Area · Conversion). The URL-match trick is
essential:
your product's URLs are per-account/per-object, so
is exactlypools only a handful of visits. Switch the matcher tocontainsand enter the path segment of your main working surface (e.g. a chat route like/c/) to aggregate the whole working surface. What the maps deliver:- Dead-click / all-click MAP → the exact ELEMENT. E.g. (
contains /c/, 30 dead clicks): assistant text referencing the record list = 40%, stat-counter chips = 13%, data-table row div = 10%. This UPGRADES a false-affordance finding from behavioral-inference to element-confirmed - the map is the confirmation layer the dead-click module's rank step points to. - Scroll MAP → the fold question, and it can return a decisive NEGATIVE. E.g. 100% of visitors "reached" every depth 5-70%, 0% drop-off → the working surface is a fixed app-shell that doesn't page-scroll; the connect CTA is not below the fold, so the funnel drop is behavioural, not placement. A ruled-out hypothesis is a real finding - it redirects the fix.
- Attention MAP shows time-per-region; degenerate when the page doesn't scroll. Two standing caveats: (1) representative-snapshot - Clarity overlays aggregated data on ONE captured DOM (often a team/internal session); layout/fold/element questions are valid across users, but the pooled DATA includes excluded workspaces (note it). (2) device-type param defaults to desktop; the date picker sometimes won't widen from the heatmap view - a 3-day aggregate is usually enough for element ranking and aligns with the 30-day census direction.
- BLIND SPOT - nested scroll. Clarity's scroll & attention maps measure DOCUMENT scroll. If the app scrolls inside a nested container (e.g. a data table with its own scroll), the maps are blind to in-container depth → "did users reach row #30 / scroll their whole list" is UNANSWERABLE via heatmap (needs replays or product instrumentation). Don't read "100% reached every depth" as "users saw everything."
- Dead-click / all-click MAP → the exact ELEMENT. E.g. (
- Funnels (code-free): build your key conversion funnel (e.g. the connect/activation funnel) in Clarity to visualize the drop without reconstructing it from your product DB.
- Inactive pages insight - surfaces surfaces nobody interacts with. Smart events / User-intent - auto-detected key actions + engagement tiers. Filters - cohort by "has JS error", "has rage click", device, country, referrer, UTM.
Coverage honesty: name which instruments you ran. "We reviewed sessions" ≠ "we used Clarity" - recordings are one of ~10 families.
Thin-session forensics (standing module - no watching required)
FIRST: strip the BOTS, or every rate you compute is fiction. Before thin sessions
can tell you anything, classify the ones that are not people at all. The signature is
sharp and mechanical: ≤2s duration · 0 clicks · 1 page · entry URL = exit URL · a
NEW browser-uid every time · clustered in off-hours. Scanners, link-preview
fetchers and security crawlers all look like this, and they hammer whatever your
unauthenticated landing page is - usually /sign-in.
(Paid for: a device-segmented pull showed "50 mobile browsers → 2 signed in", which
read as a catastrophic 4% mobile sign-in wall. Classifying the population first:
38% were ≤2s/0-click bots, most of the rest were sub-12-second bounces, and the
real engaged-mobile population was ~9 sessions in 30 days - about five humans.
The "wall" was bot traffic in the denominator. The single "sign-in failure" left
standing turned out to be one real user's first of two attempts; he got in on the
second. A whole segment-level finding evaporated - correctly.)
Report the bot share explicitly, and never let a device / channel / page segment
carry a rate until its bots are stripped: a segment that is mostly bots will show a
spectacular conversion collapse that means nothing.
Thin sessions (<30s, ≤1 click; 50-60% of all sessions on our runs - measure your own share) are a separate instrument, not noise. Aggregate them (Python over the pull JSON) by entry-URL class × referrer domain × browser-uid × duration, with uid-level exclusion (any uid that EVER touches an excluded workspace is excluded everywhere). What they carry:
- OAuth bounce-backs: referrer
<your OAuth provider's hosted-auth domain>→ returns from the hosted connect flow (the detector re-found all three of one user's touches). - Parked-tab heartbeats: thin sessions entering directly on a working-surface URL = the tab is still open - a live, free re-engagement surface; series per uid measure it.
- Auth loops: a uid with ≥3 sign-in-entry sessions and no workspace ever = someone repeatedly failing/abandoning sign-in (watch ONE of them).
- Acquisition census (poor-man's): entry×referrer distribution - e.g. thin sessions referred from your marketing site = the marketing→app handoff leak, measured.
- Email-click forensics:
utm_in entry URLs separates human email clicks from scanner hits (and near-zero utm sessions corroborate "email retrieves no one"). - Competitor recon: repeat thin sessions from known competitor accounts.
Output: data/thin_forensics.json (aggregates + repeat-uid table + bounce list) -
feeds the synthesis alongside classifications and watch observations.
UI-clarity review - audit what the UI SHOWS, not only what users click (standing module)
Maturity: the SOURCE-LINT below is mechanical and proven (4 issues from one file). The cold-read + frame parts are a MATURING instrument - one catch so far, by a human not the method; detectors still validating. Don't present frame-flagged items at the same confidence as source-linted ones.
The behavioral pipeline (clicks, dead clicks, selections) is STRUCTURALLY BLIND to problems that produce NO event: a misleading label, a status with no next action, a stage-name used as a status. The user misreads it or gives up, and never clicks. (Real instance: a status column showed a red label whose text implied a pending user-goal but actually meant "no reply in 7 days" - one label for three opposite states; the pipeline had watched that account but was scoped to clicks.) Run these in order - most mechanical first.
1 - Source lint (LEAD here - mechanical, coverage-independent, diffable)
Where status labels resolve centrally (e.g. a single status-resolver module in your frontend source), lint the state table for invariants:
- (a) No label maps to >1 state/semantic. One
statusLabelreused across success / danger / disabled ⇒ the meaning lives only in colour + tooltip. (The real instance: one label mapped to success at one resolver line, danger at another, disabled at a third - while a clear unambiguous label for that state already existed elsewhere in the same resolver.) - (b) Every user-ball state exposes a next action. A state the user can act on (Connected → send; No reply → follow up; Replied → reply) but the UI gives no control.
- (c) Meaning survives colour + tooltip stripped. If removing colour and hover leaves the label ambiguous, it fails.
- (d) No affordance may depend SOLELY on hover/focus - those events do not exist on
touch. Lint every interactive-looking surface for a root whose only handlers are
onMouseEnter/onMouseLeave/onFocus/onBlurand which has noonClick. On a phone that surface is inert and never gives the feedback that would tell the user it is inert. (Real instance: a big onboarding card whose root played a video on hover to signal "I'm alive" and carried no click handler - only a small inner button worked. On desktop the hover-video partly rescued it; on mobile it was a dead image. A watched iPhone user tapped the card body twice, got nothing, and ended up in the wrong flow.) Coverage-independent and diffable - the lint's strongest suit, since no amount of desktop replay-watching reveals it. Output: file:line + the failing invariant. Coverage-INDEPENDENT - audits states that never rendered in any watched session (Failed/Canceled variants often don't); it will even catch a misspelled status enum for free. The real fix is upstream, not here: these invariants belong as a UNIT TEST in your frontend codebase (assert no resolver step-variant shares a label with a different semantic; assert every user-ball state exposes an action) - catches the class at commit time, forever, reviewer-independent. The skill CONSUMES that lint's output; it doesn't substitute for it. Recommend the test; owner = the owning frontend engineer. Limits (state them): detects incoherence, not incomprehension - a label used for ONE state can still read as a CTA and mislead (passes the lint, fails the user); blind to styling/juxtaposition (a "Connection sent" pill styled like the live button beside it is a CSS fact no label-lint sees); misses scattered inline JSX, i18n catalogs, and backend/LLM-served strings; no prioritization (enumerates every incoherence equally).
2 - Cold-read comprehension probe (makes "read it as a user" falsifiable)
"Read each label as a user" is taste until you instrument it. Give a CONTEXT-FREE agent (ideally 2-3) the frame and ask "what does each label mean, and what would you do next?" Diff against source truth. Mismatch = a comprehension defect with a measurable rate ("3 of 3 cold readers read the red status label as a pending goal; source says no-reply"). This is the prediction-vs-ground-truth version of "label doesn't mean what it says" - same spirit as the refuter pass. Catches what the lint can't (incomprehension).
3 - Frames for composition (what source can't see)
From captured frames, flag control-styled elements in action contexts and elements styled like an adjacent live control (the status-pill case). Juxtaposition/styling class; also your unknown-unknowns pass.
Real confusion signals you own (NOT the imagined hover map)
- Clarity has NO hover heatmap - verified: Click / Scroll / Area / Conversion / Attention maps only; hover is Hotjar. Do NOT cite one (inventing an instrument is the exact error the dead-click gate forbids). For hover-linger use the per-session pointer-trace poller (Layer 3) with its validation gate, labeled OBSERVED - never aggregate.
- Grep the confusing label string in your product's conversation-message table + your support/feedback threads - users who don't understand a label ask about it IN WORDS. Real, existing, MEASURED-tier, zero new instrumentation.
Reporting gate (against false-positive flood)
A UI-clarity finding is reportable ONLY with (a) a prod-DB PREVALENCE number (how many real leads/users sit in that state) and (b) a stated COST (wrong next action / silent abandonment). "Needs a hover to understand" would otherwise flag half of any SaaS UI (icons, timestamps, truncation) - the gate keeps it honest. Report "states never observed rendered" from the source enumeration so coverage isn't overclaimed.
Complement of dead-click analysis: dead-click = "acted, nothing happened"; UI-clarity = "would be misled / can't act, but never clicks."
Dead-click analysis (standing module)
Dead clicks are the cheapest UI/UX-debt instrument we have - a dead click is a user telling you, without words, "I expected this to do something." Every recurring dead-click target is a mismatch between perceived and real affordance; the fix direction depends on which way the mismatch runs. Run this every review - it needs no watching (API + dashboard), and it repeatedly surfaces issues the funnel numbers hide.
Verify-first gate - a dead-click COUNT is NOT a defect count (calibration)
Clarity's "Dead click" label fires on THREE different behaviors and only one is a defect. Before citing any dead click, classify it - cheaply, from the events panel:
- Copy-select drag-start → the click is the start of a text selection. Tell: an
adjacent
Selected textevent (often a whole RUN of them). This is copy-out, NOT a defect (route to the copy-out finding). One session - the "dead click" sat inside a run of Selected-text events; a false positive. - Reading-tap on prose being read → stray taps on a message body / record-card description while reading, spread out, no selection, no repetition. Low signal. Another - taps on inert record-card title / rationale prose.
- Genuine false affordance → a control-styled element (button/pill/tile) in an action context, clicked REPEATEDLY with no reaction. This is the defect. A third - a green "Connection sent" status pill, styled like the live action button beside it in a suggested-actions row, hammered 5×.
Gate: (1) is there a real dead-click EVENT in this session (not just a classifier's hypothesis - two sessions had ZERO dead-click events yet were cited); (2) is it copy-select or reading-tap (drop it); (3) only a repeated click on a control-styled target in an action context survives as a defect. The automated watchers over-called this class; a human watch caught it - this is exactly the Stage-5 role. Don't trust the count; verify each target.
Cross-instrument gate - check UNITS and POPULATION before believing a contradiction
(Paid for, and it is the easiest self-own in the method: comparing one instrument's logged-in users against another's sessions, I "discovered" that our analytics was blind to mobile. It wasn't. Same-unit comparison - sessions vs sessions - showed the two instruments agreeing within 15%. The false contradiction was a units error, and it very nearly shipped as a finding about the instrument.)
Before publishing ANY "instrument A contradicts instrument B" claim, clear four gates:
- Same unit. sessions ≠ visits ≠ browsers ≠ people ≠ logged-in users. Name the unit in the sentence; if the two numbers have different units, there is no contradiction yet.
- Same window and timezone, stated.
- Same population filter - one instrument's number is usually pre-login-inclusive, bot-filtered, or team-excluded when the other's isn't.
- Population overlap, spot-checked by NAME. Can instrument B even see the specific individuals A found? Look up 3-5 named users in both. Blindness is real (ad-blockers, ITP, in-app webviews) - but prove it per-user before asserting it in aggregate, and remember it makes B's counts floors, not fictions.
A contradiction that survives all four is a finding. One that doesn't is arithmetic.
Before any RATE ships - four more gates (the denominator is a claim)
(SKILL.md principle 6. The cross-instrument gate above protects you when two numbers disagree. These protect you when a single number is quietly meaningless - which is the more dangerous case, because nothing contradicts it.)
Write these four answers next to the rate, in the doc, before you publish it. Each one has killed a finding that had already survived a census, a source read, and review.
- Is the denominator people? Bots, team/test seats, fragmentation, hidden-tab inflation. Strip bots before any segment carries a rate - a mostly-bot segment produces a spectacular, meaningless collapse. (A "mobile sign-in wall" died here: 38% of the segment were 2-second scanners.)
- Could every member have done it, for the whole window? Find the ship date of
the affordance and compare it to the window. A rate whose denominator spans a
product change is fiction. ("137 accepted invitations, never followed up" - the
send button did not exist for most of that window. The claim had survived three
passes; it died to one
MIN(created_at)and onegit log.) - Did anyone attempt it? ← the trap that survives 1 and 2. An empty cell has two causes - nobody tried, or everybody failed - and they are identical in every instrument you own. Prove attempts before you explain a zero. If you cannot, the missing instrument is the finding: log the START of the step, not just its success. (A "broken vendor integration" and a whole strategy on top of it died here: the flow was fine; nobody had tried in nineteen days. Execution settled in three minutes what three days of forensics could not.)
- Was the window chosen before you saw the data? Re-cut the same claim by month before you publish it. ("80% broken" read 75% / 80% / 20% / n=0 attempts once split.)
Cheap mechanical check for gates 2 and 3, run it every time: for any rate of the
form "X of the people who reached step N did/didn't do step N+1", run
MIN(created_at) on the table that records step N+1 and git log -S on the control
that triggers it. If either postdates the start of your window, the rate is an era
artifact and the honest denominator is only the users who arrived after the feature
did. This one query has killed two published findings.
The dead-click gate is one row of a general claim-type verification table - write the row before trusting any classifier signal. For each claim class: required raw evidence · known false-positive modes · alternate explanations that must die. E.g. dead-click → {a real Dead-click event exists} · {copy-select drag, reading-tap} · {is it actually a selection? a working control clicked once?}. never-returned → {no session cross-day} · {adblock invisibility, cross-account return} · {did they return under another uid?}. Adding a claim class means adding its row first; a signal with no row is a hypothesis, not a finding.
Taxonomy (from one product's census - 164 dead clicks / 23 sessions, floors - pre-gate, so counts are upper bounds that include copy-select + reading-taps). The CLASSES are general; the named instances are one product's (an AI outreach tool) - map them to your own domain rather than hunting for these exact elements:
- Whole-surface false affordance - the biggest, most actionable class. A card / tile / banner / list-row that contains a small inner CTA, but the whole visual container reads as clickable - so clicks land on the body, image, heading, or padding, not the button. Fix = make the ENTIRE surface clickable (the opposite of "make it inert"). Named instances: two onboarding welcome cards - each a headline with a small inner CTA - where Clarity click-dots cluster on the banner body, not the button (flagged from a shared recording - users appear to click the banner body, not the small button); an onboarding tile (one user dead-clicked it). The strongest confirmed instance: a "Connection sent" status pill - green, checkmark, styled exactly like the live action button beside it in a suggested-actions row, hammered 5×; the user wants to act on the sent request (open the message / thread), the pill is inert. (A record-card title / rationale prose is also inert and draws stray taps, but weakly - that's reading-tap territory per the gate above, not a crisp defect.) Detection: cluster dead clicks by nearest interactive ancestor - if a real CTA shares the container, it's this class.
- Pseudo-interactive text - make it honest. Agent reasoning/"Thoughts" lines, criteria chips, drafted-message text that users select-copy (copy is INFERRED - clipboard is invisible). Here the fix runs the other way: either add a real affordance (a copy button, an expand toggle - the copy-out demand is genuine) or make the text visibly non-interactive so it stops inviting clicks.
- Disabled controls during async work (e.g. while an AI agent or a long job
runs) - users hammer a disabled Send / primary-action button (watched: 3 dead
action-button clicks in one session).
Clarity's Dead-click label is unreliable here (any DOM reaction suppresses it);
instrument a
*.DisabledClickevent for an unambiguous impatience signal, and consider queuing the message + a "you can type while I work" affordance. - Status chips/steppers styled as actions - no target (a sub-case of class 1, the confirmed one). One user hammered the green "Connection sent" pill 5× and, in another session, clicked the "Pending acceptance" stepper - both are outreach-STATUS indicators dressed as actions. The user wants to act on the sent request (open the message / thread / advance it). Fix: make it live (deep-link to the pending request / queued message) OR de-button it to a plain status chip with the next step stated inline ("waiting for X to accept - we'll message automatically"). The "Trial x/xx" badge is the softer version - clicking the counter = wanting a limit/upgrade panel that doesn't open (a feature gap, and often NOT labeled a dead click, so verify the event exists before citing it).
- Translate/locale artifacts - auto-translate breaking React interactivity.
- Rage-adjacent repeats (same target ≥3×) - track via the rage-click filter.
Dead-click analysis is a MANDATORY separate section in every report (not folded into patterns). Its procedure:
- Rank targets by frequency - use the aggregate dead-click metric
(
query-analytics-dashboard: "top pages by dead click count") as the ranking layer, NOT per-session floors (untruncated, cross-session). Exclusions apply. Then drill to the element via the dead-click heatmap. - Classify each top target (the 6 classes above) and tag a fix direction: whole-surface-clickable / add-affordance / make-inert / link-it / instrument.
- Triangulate each top target two ways - this is what turns "people click here"
into a confirmed defect:
- Frontend source (the decisive check): grep your frontend source for the
element's text / component and confirm whether it actually has an
onClick, is wrapped in a link/<button>, or is inert markup. A dead click on an element the source shows has NO handler = a proven false affordance (this is the check run manually on the welcome banners → "confirm the area is not clickable"). Cite the file:line. - Product DB: does the action the user was reaching for have any backing? (e.g. a dead-clicked "Connection sent" chip → is there a message-action row it could link to; a record-card click → did that record get acted on).
- Frontend source (the decisive check): grep your frontend source for the
element's text / component and confirm whether it actually has an
- Output: a ranked table - target · surface · dead-click count (floor) · class · fix direction · source confirmation (file:line, has-handler y/n) · session links.
Monthly cadence: track the dashboard dead-click % as the standing UX-debt KPI; every new surface gets a dead-click check 2 weeks post-ship.
Discovery modules - balancing the question portfolio
Added after a four-perspective product-expert review (2026-07) whose consensus was: the forensic pipeline is excellent and points the player only at FAILURE signals - drop-offs, dead clicks, anomalies. A method that only asks pathology questions is structurally blind to everything else replays uniquely show: how a first-timer's comprehension forms, what intent gets typed and abandoned, how successful users actually work, and what changes between visits. These four modules add those question families.
Guardrails (non-negotiable). Every module runs THROUGH the existing discipline -
Stage-0 question, evidence tiers, appendix rows, the refuter - never around it.
Before using any NEW claim class, write its claim-type verification row (required raw
evidence · known false-positive modes · alternate explanations that must die); a
signal with no row is a hypothesis. The pointer-trace poller STAYS gated: no
hesitation / approach-retreat claims unless a named decision needs sub-second
dynamics AND the Stage-3 validation gate passes - motionless-reading windows and
Page hidden attention carry most of the comprehension signal at frame-tier cost.
First-session comprehension watch - EXPERIMENT
Status: experiment, 2 periods. Promote to standing only if it produces findings that change decisions at ≥ the flagged-set rate.
Every genuinely-new external human's first engaged session gets ONE full 1× watch, skip-inactivity OFF - exempt from the triage gate and the per-workspace watch cap. "Genuinely new" is an attribution claim: verify a sign-up flow and dedup cross-account humans (principle 2) before calling anyone new. At low traffic this is a handful of sessions per period - the cheapest attack on activation there is.
Harvest (replay-unique: the database shows the activation OUTCOME; only the tape shows the PATH):
- what was on screen during motionless-READING windows - what got read vs skipped;
- the first thing tried, and the gap between what onboarding DISPLAYS and what the user ATTENDS to.
The output contract - DO NOT ask for "the moment comprehension broke"
(Paid for. The first run of this module required a field called "the moment comprehension broke". The watcher produced confident motive stories; on the mandated human re-watch the reviewer agreed with the BEHAVIOUR 2/2 and said "not sure" about the motive 2/2. Of course he did - motive is never on tape; it is in this skill's own blind-spot list. The module was asking for an unfalsifiable field, which is precisely the confident-wrong-story failure the whole skill exists to prevent.)
Each watched first session outputs exactly three things:
- The DIVERGENCE POINT (OBSERVED - behavioural only). Timestamp + frame where the user's behaviour departed from the designed path, stated with no mental state in the sentence: "opened the example, then opened the menu, then left - never typed", NOT "did not understand that he had to type". If you cannot write it without a verb like understood / realised / expected / was confused, you are inferring. A verifier must be able to confirm this line from the frames alone.
- AT LEAST TWO competing explanations (INFERRED - labelled). Never one. One explanation is a story; two force the uncertainty into the open. ("(a) the pre-filled composer read as already-done; (b) low intent - a 10-second tyre-kick.")
- The PROBE. The single question - to the user, or to a cold-reader - that would discriminate between (a) and (b). This is the deliverable that closes the gap; the watch alone never will.
The validity check tests #1, not #2. Each period, a human re-watches a sample and confirms the DIVERGENCE POINT; publish that agreement rate. Never ask a reviewer to ratify a motive - they will say "not sure", and they will be right.
Known false-positive modes (the claim-type row): a backgrounded tab is not stalled
comprehension (read Page hidden/visible first); auto-translate artifacts; a
"first session" that is a cross-account return; masked text misread as ignor...
references/periodic-review.md
Periodic review - reviewing ALL sessions in a window (the recurring mode)
Mode B of the skill (Mode A = targeted investigation). Goal: a recurring
"session-intelligence report" - the Voice-of-Customer analogue for product behavior -
over EVERY session in a time window. Born from a real run over 462 sessions; its
report generator is the reference implementation (see ../examples/) - the workflow
script itself remains internal. Same seven principles as Mode A; the difference is
orchestration at scale.
Scope decision (state it up front)
"Review ALL recordings" = census 100% · replay-watch the flagged set · triangulate 100% of findings. Watching every session is serial browser time spent mostly on hidden tabs and 10-second bounces. The census is the pattern instrument; replays are the microscope for what the census can't explain. On our first run (~460 sessions) the flagged set was ~40 sessions, ~5-8% of the pull; expect flags to scale with your pull and the pattern library to shrink them each period. At high traffic (thousands of sessions per week) the 100%-census arithmetic here no longer fits in one pull: census via the aggregate dashboard layer, then SAMPLE the watch population - stratified by funnel stage or entry surface - instead of enumerating it.
The shape - four phases; the middle two are ONE resumable Workflow
0. SCOUT inline Python, no agents pull → enrich → batch ~15 min
1. TRIAGE parallel agents (Workflow) classify all from timelines ~10 min
2. WATCH SERIAL agents (Workflow) replay-watch the flagged set the bottleneck
3. SYNTH+REFUTE parallel agents (Workflow) patterns → refute → report ~15 min
Run phases 1-3 as a Workflow so the run is resumable (survives session limits).
Phase 0 - scout (inline, deterministic, no agents)
- Pull via the direct API / Clarity MCP. The
countcap is 250 - at ~100-150 sessions/day that's a few days of traffic, so a 2-week window needs several day-slices with explicitstart/end(ISO-8601 UTC) and dedup on session link; scale the slice width to your own volume. The API 503s under load - back off ~90s and retry; don't hammer. Save the raw pull JSON: it preserves player links even after the recording expires from retention. - Enrich: join every workspace/account UUID to your product database → emails, subscription status, usage. Apply your canonical exclusion list.
- uid-level exclusion: any browser-uid that EVER touches an excluded workspace is excluded EVERYWHERE (principle 2). Compute the excluded-uid set once, propagate.
- Compact the timelines (strip full URLs to relative paths - full URLs get output-blocked by the harness's cookie/query filter) and cut into ~25-session batch files for the fan-out.
Phase 1 - triage fan-out (parallel agents)
One classifier per ~25-session batch, schema-forced output (class, flag, excluded,
ws, emails, question, window, priority). Give every classifier the skill's triage
rubric + a known-actors list (named users, competitors, prior-cohort members,
already-watched keys) so classes stay consistent across batches and nothing is
re-watched. Merge inline: dedup, count, build the watch-list = flagged ∧ not-excluded,
priority-sorted, capped ~2 per workspace so one heavy user can't eat the watch
budget - with two EXEMPTIONS from the cap (see the discovery modules in method.md):
first sessions of genuinely-new humans, and first-week accounts (a new signup's week-1
arc is the richest artifact at low traffic; the cap must not slice it into
disconnected samples).
Phase 2 - watch (SERIAL - the hard constraint)
The browser is a single shared resource and background tabs throttle → watchers run
one at a time in a loop, never parallel (for … { await agent(…) }). Two tricks:
- Fresh context per watcher (a new agent per session, not one long agent): scre...
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.