Engineering note

“Reading every session”
sounds expensive.

It would be, done naively. Feeding raw event streams to a model is the obvious implementation and the one that makes margin collapse the moment anyone actually uses the product. Here is what GhostView does instead.

The reduction pipeline

01

Capture

hundreds of events / session

rrweb DOM mutations plus named interaction events, streamed from the embed. Inputs are masked at the source and no IPs are stored, so the sensitive material never enters the pipeline in the first place.

02

Reduce, deterministically

~1 summary object / session

computeSessionAnalytics() walks each event stream in plain code — no model involved — and emits one compact summary per session: duration, page visits, idle gaps, click count, error count, and rage-click detection. Rage-clicks are found by a timing heuristic, not by asking an LLM to notice them.

03

Synthesise across sessions

1 model call / run

A single model call reads every session summary together and returns structured JSON — friction points, behavioural clusters, a prioritised fix list, and an executive summary. It is the only stage that sees the whole corpus, and it never sees a raw event.

The important property: the expensive stage sees a fixed-size input no matter how long your users stayed. A 40-minute session and a 40-second one cost the same to reason about, because both arrive as a trace.

What it actually costs

Raw events sent to a model

None

Stages 1–2 are deterministic code. The model only ever receives computed per-session summaries — which is simultaneously the cheap path and the smaller privacy surface.

Cost driver

Session count

Prompt size grows with the number of sessions, not with how long anyone stayed. A 40-minute session and a 40-second one reduce to summary objects of the same size.

Per-run inference

Cents, not dollars

A synthesis run over a few dozen sessions is a single call with a few thousand tokens of input and a 4k output cap. The expensive naive design — streaming raw events into context — is the one we specifically do not do.

What actually scales badly

Storage

Replay payloads, not inference, are the cost that grows with usage. That is why retention is a published number rather than an unbounded promise.

Published because “AI feature with unexamined inference cost” is a fair thing to be suspicious of. These are structural properties of the pipeline, not projections \u2014 you can read the shape of it in the reduction stage above.

The output is for machines too

The agent that built your app
should be the one reading this.

If an LLM generated your checkout flow, a human copying findings out of a dashboard and back into a prompt is a silly place for the loop to break. Synthesis findings are structured, cite their evidence, and are retrievable as JSON — so the coding agent can consume the friction report directly and propose the fix.

Findings, structured for a consumer that isn't a person:

POST /api/synthesis { "projectId": "…" } { "projectId": "…", "sessionCount": 9, "synthesis": { "frictionPoints": […], "patternClusters": { "strugglers": […], "completers": […] }, "prioritizedFixes": […], "executiveSummary": "…" } }

Same findings you see on the demo page, addressed to a different reader.