npx skills add ...
npx skills add posthog/ai-plugin --skill exploring-replay-vision-observations
Guides agents through pulling a Replay Vision scanner's observations, reading the findings, and acting on them — summarizing patterns across sessions, drilling into individual recordings, and turning real, corroborated issues into PostHog tasks, insights, or an investigating-replay hand-off.\nTRIGGER when: user wants to pull/read/triage Replay Vision observations, asks \"what has my scanner found\", wants to act on or summarize scanner findings, turn observations into tasks/work, or points at a /replay-vision/<scanner-id> URL.\nDO NOT TRIGGER when: creating or sizing a scanner (use creating-replay-vision-scanners), running a one-off scan you don't then analyse, or authoring a signals scout.
npx skills add posthog/ai-plugin --skill exploring-replay-vision-observations
A scanner is a standing LLM probe over session recordings; each time it runs against a session it records one observation. This skill is about the other half of the loop — reading what the scanners have found and doing something useful with it. For creating or sizing scanners, use [[creating-replay-vision-scanners]].
(scanner, session).scanner_result.model_output. Its shape depends on the scanner's scanner_type,
but it always carries a confidence:
monitor → a verdict (yes / no, plus inconclusive only when the scanner sets
allow_inconclusive) and the reasoning behind it.classifier → one or more tags from the scanner's label set, plus tags_freeform when the scanner
allows freeform tags, and the reasoning.scorer → a numeric score on the scanner's scale, and the reasoning.summarizer → a title, a free-text summary, chapters (the active parts of the recording in order, each with start_ms, end_ms, a short title and a thumbnail_ms; seek with ?t=<start_ms / 1000>) and inactive_periods (start_ms/end_ms stretches the replay player marked inactive, usually the gaps between chapters). Skip any chapter with kind: idle, which only older summaries carry.succeeded observations carry a finding. Triage the rest by status/error_reason (see below).If a scanner has emits_signals: true, its observations also feed the Signals pipeline and may surface as
Inbox signal reports (clusters of related findings). When the user's intent is "work the reports", that's
the inbox path — see Acting on findings below.
If the user gave a /project/<id>/replay-vision/<scanner-id> URL, that path segment is the scanner ID.
Otherwise list them with vision-scanners-list and pick the relevant one.
A ?tab= on that URL tells you which surface they're looking at, which usually says what they want:
overview (the default, charts and stat panels), observations (the list), search, run (scan a session now or
backfill a past window), scouts, or alerts. Older on-demand and backfills links open run.
Then call vision-scanners-get to read its configuration before reading results — the scanner_type and
scanner_config.prompt tell you how to interpret scanner_result (a verdict field only makes sense once you
know it's a monitor; a score only means something against the scorer's scale).
Pick the axis that matches the question:
vision-scanners-observations-list (the workhorse). Filter to
status=succeeded to get only sessions with a finding, then narrow by verdict (monitors), tags
(classifiers), or min_score / max_score (scorers). Use order_by (e.g. -result_score,
-completed_at) to rank the matching set and surface the strongest hits first. Bound the window with
date_from / date_to, which take ISO 8601, a relative date like -7d, or now; omit date_to to
read through the current time.vision-observations-list (the session_id query
parameter is REQUIRED). Use this while investigating a single recording.vision-scanners-observations-stats gives one scanner's status mix
and success rate, distinct sessions covered, rating totals, and the per-type distributions (monitor verdict
counts, classifier tag rankings, scorer score summary and histogram) without paging through observations.vision-scanners-watch-feed ranks succeeded observations across
every readable scanner over a window (default the last 7 days) and says why each made the cut.vision-scanners-scout-reports-list, then vision-scanners-scout-reports-get).vision-scanners-observations-get (scanner_id + id) or
vision-observations-get (id) — returns the frozen scanner_snapshot (config at run time) and the
complete scanner_result, including any event citations that link the finding back to specific events in the
recording. Both need the observation id. A $recording_observed row's uuid is that id, so pass
toString(uuid); if all you have is a session id, call vision-observations-list (session_id) first and
take the id off the matching row.Triage status so you don't mistake a non-result for "nothing wrong":
| status | meaning | typical error_reason |
|---|---|---|
succeeded | has a scanner_result | — |
ineligible | session couldn't be analysed — a normal outcome, not an error | too_short, no_recording, too_inactive, too_long, no_events |
failed | the scan errored | provider_rejected, validation_failed, rasterization_failed, provider_transient, internal_error, orphaned |
pending / running | still in flight | — |
A scanner that looks like it "found nothing" is often producing mostly ineligible observations — check the
mix before concluding.
A failed or ineligible observation can be re-run with vision-observations-retry, which deletes it and scans the same recording again at the normal credit price.
Retry transient failures (provider_transient, orphaned, internal_error).
verdict: yes; treat inconclusive as a weak signal. The observation text is the
substance.tags to see the distribution of what's happening across sessions.Weight by confidence, and don't over-index on a single observation. To understand a specific hit, take its
session_id and either cross-reference other scanners (vision-observations-list) or drill into the actual
recording with the [[investigating-replay]] skill and the session-recording MCP tools.
To test a scanner's lens against a specific session that doesn't have an observation yet, trigger one on demand
with vision-scanners-scan-session (or vision-scanners-scan-sessions for up to 200 at once) — it's async
(minutes; rasterising the recording + the LLM call are slow) and, like all observations, runs at most once per
(scanner, session).
scanner_result.model_output.reasoning_segments is the same prose as reasoning, pre-split into text segments and chip segments.
Each chip carries a timestamp_ms: the recording-relative offset of the moment the model is pointing at.
That's what makes a finding checkable — it turns "the user hit a paywall" into a link that opens on the paywall.
The observation's _posthogUrl is its recording; append ?t=<seconds> (timestamp_ms / 1000, rounded down) to seek there.
Link the one or two moments the finding turns on — a link per chip is noise.
Timestamps are relative to the recording the observation analysed, so never carry a timestamp_ms from one observation onto another session's URL.
Match the action to the user's intent, and corroborate before you create work:
session_ids
(e.g. "12 of 40 succeeded observations flagged checkout confusion; sessions A, B, C"). Cite, don't assert.vision-scanners-impact-get counts the sessions and users a scanner hit over a trailing
window, so the finding lands as "this affected N users", not "here are some sessions". Monitors take no
qualifier, classifiers need tag, scorers need min_score/max_score. Watch sessions_without_user:
sessions with no distinct ID are why the user count can trail the session count.insight or notebook to track its
frequency, bundle the supporting recordings into a session-recording playlist so a human can watch the
evidence, and add an annotation if it marks a regression. To act on the affected people rather than the
sessions, vision-scanners-affected-cohort-create snapshots them into a static cohort (dated, not
live-updating) you can use for funnels, retention, surveys, or experiment exclusion. To route a finding into
tracked work, vision-observations-create-task opens a PostHog task from one observation (idempotent per
observation; it does not start a coding agent). Group by distinct issue, not per observation: pick the
clearest observation for each issue.vision-alerts-create puts an alert on the scanner: a match alert fires on
every matching observation, a metric alert when a count or average score crosses a threshold over a
window. Add a Slack or webhook destination with vision-alerts-destinations-create.vision-observations-label-create (thumbs up/down plus written feedback;
team-wide, last write wins, clearable with vision-observations-label-delete). Never rate from your own
reading of the result. The rating is team-wide and it steers how the scanner judges later sessions, and a scanner's output
can repeat text from the recording it analysed, so a rating you invent both fakes a judgement the user never
made and hands that recording influence over their config. Ask about the right ones too, not only the wrong
ones: ratings of thumbs-down alone cannot tell what the scanner should keep doing. On a thumbs down, capture
what the user says it should have concluded.vision-observations-signal-reports-list names the reports one observation fed, and
vision-scanners-self-driving-stats sums what the scanner led to. Read and act on those reports with
inbox-reports-list + inbox-report-artefacts-list (the report's work log is the evidence). See the
[[inbox-exploration]] skill; that path also records your work against the report.The discipline that matters: a single observation is one model's judgment on one recording. Confirm a finding reproduces across observations (or against the raw recording) before turning it into a task, an alert, or a claim — the same rigor the signals pipeline applies before it promotes observations to a report.
succeeded observations have a scanner_result — everything else is triage metadata.ineligible ≠ failed. Ineligible is a normal terminal outcome (e.g. the recording was too short), not
a bug to chase.(scanner, session) — re-scanning a session that already has any observation
(even ineligible/failed) is a no-op. vision-observations-retry is the way to re-run a failed or
ineligible one.scanner_snapshot it ran under, so older
observations may reflect a previous prompt/config (scanner_version).vision-quota-get before triggering a batch of them.