M A R K · I N T E R C E P T O R
Watching the reflection of Facebook watching us. A way to look inside of the algorithm. v0.2.0 — SUPERCHARGED
+ ╔════════════════════════════════════════════════════════════════╗ + ║ Capture your own Facebook session telemetry. ║ + ║ Watch it render as a 3D cosmos. ║ + ║ Let the mirror tell you who you are when the algorithm ║ + ║ is watching. ║ + ╚════════════════════════════════════════════════════════════════╝
A two-part, localhost-only toolkit for algorithmic self-research:
- SESSION·COSMOS — captures the hidden telemetry fields Facebook/Meta attaches to every request, and renders them live as a navigable 3D space you can fly through.
- REFLEX — reads the captured stream back offline and produces a single self-contained HTML portrait of the feedback loop between you and the ranking algorithm. 13 analysis panels covering surface transitions, hook moments with permutation-tested significance, hover→action conversion, ad dose-response, time-to-action survival curves, and the rarely-seen self-surveillance meta-layer — Facebook's own telemetry of you, captured from inside your browser.
Pure Python stdlib backend. Zero build step frontend. Nothing leaves your machine. No calls home.
▓▒░ ENTITY FINGERPRINT ░▒▓ captures : localhost only exfil : none decoder : 118 patterns / 12 categories / 97.9% coverage on real data stats : MAD bursts · Welch periodogram · Laplace Markov · permutation tests outputs : ONE html file · no JavaScript framework · opens with a double-click
Table of contents
- What you get
- What it reveals
- $\color{#ff4fd8}{\textsf{What data we capture}}$ ◀ new in v0.2.0
- $\color{#ff4fd8}{\textsf{The 13 analysis panels}}$ ◀ new in v0.2.0
- $\color{#ff4fd8}{\textsf{Taxonomy — 12 categories}}$ ◀ new in v0.2.0
- $\color{#ff4fd8}{\textsf{Statistical rigor}}$ ◀ new in v0.2.0
- Quick start
- Architecture
- Session Cosmos in depth
- Reflex in depth
- Repository layout
- Privacy and ethics
- Troubleshooting
- Platform notes
- Roadmap
- Contributing
- License
What you get
| Component | What it does | Runs in | Inputs | Outputs |
|---|---|---|---|---|
cosmos_relay.py v2.0 | WebSocket broadcaster + HTTP ingest + NDJSON recorder + multi-kind event routing + monotonic __relay_seq stamp | A terminal (Python 3.7+) | HTTP POSTs from the userscript | Live WS stream + session.ndjson |
session_cosmos.html | 3D visualizer (Three.js, single file, no build step) | Your browser | WS stream from the relay | A flyable cosmos |
facebook-interceptor.user_1.js | Userscript v1.1 — hooks fetch / XHR / sendBeacon, extracts blob fields and activity names | Tampermonkey | Your browsing | POST requests to 127.0.0.1:8766/ingest |
facebook-interceptor.user_2.js v2.0 | Supercharged — adds response latency, localStorage buffer (survives relay restarts), scroll-velocity sampler, visibilitychange sensor, per-tab id, @match for Instagram + Threads | Tampermonkey | Your browsing | Request + input + visibility events to relay |
reflex.py v0.2.0 | >td >A terminal (Python 3.9+) | NDJSON (one blob per line) | One report.html | |
reflex_discover.py new | Char-trigram Jaccard clustering of unmatched friendly_names → auto-suggests regexes + categories + icons | A terminal | NDJSON file(s) | Python snippet ready to paste into ACTIVITY_PATTERNS |
reflex_live.html new | Real-time portrait: rolling-window analyses re-computed every 750 ms from the live WebSocket stream | Your browser | WS stream from the relay | Live mirror in the browser |
generate_sample.py | Deterministic synthetic session generator | A terminal | Seed + event count | Synthetic NDJSON |
Everything is stdlib only on the Python side. No pip install. No virtualenv. No external network.
What it reveals
On the included synthetic sample (Reflex/sample_session.ndjson, 800 events, seed 1729), Reflex surfaces:
- Next-surface prediction 89.3% accurate versus a 35.8% baseline → 2.49× lift
msgcontent is 2.40× more likely to appear in the five events immediately before you take an action than baseline expects- Peak activity at 14:00 UTC, accounting for 31% of the session
- A dominant ~45-minute rhythm in attention oscillation
On your own captures you'll see your numbers, not these. That's the point.
+ ┌─── v0.2.0 DEMONSTRATED ON REAL DATA ─────────────────────────┐ + │ 1,979 events · 10 session segments · 4 bundle revisions │ + │ DECODER COVERAGE .......... 33.0% → 97.9% │ + │ MSG hook ............ 2.46× · p = 0.002 *** │ + │ SEARCH hook ......... 2.33× · p = 0.002 *** │ + │ HOVER conversion .... 1.38× baseline within ~4 events │ + │ PREDICTION (5-fold CV) ........ 92.5% ± low-variance │ + │ FB self-surveillance captured ........ 76 events │ + │ PRESENCE pings captured ............. 148 events │ + │ FREE A/B tests found inside capture ..... 3 rev boundaries │ + └──────────────────────────────────────────────────────────────┘
A rendered portrait of the sample lives at Reflex/sample_report.html. The live-updating viewer lives at Reflex/reflex_live.html.
What data we capture
Every entry in the NDJSON log is one captured request, sensor event, or visibility transition. The interceptor extracts these exact fields — nothing else leaves the page context, nothing you'd call "content" or "personal data beyond the envelope" is touched.
Structural fields (the telemetry envelope)
+ ┌─────────────────┬──────────────────────────────────────────────────┬──────────────────────────┐ + │ FIELD │ WHAT IT IS │ USED IN │ + ├─────────────────┼──────────────────────────────────────────────────┼──────────────────────────┤ + │ __req │ Monotonic client-side request counter (base-36) │ Seq ordering, dedup │ + │ __crn │ Comet route — which Facebook surface │ 02 transitions · 06 pred │ + │ __rev │ Bundle revision — which code shipped to you │ 09 rev boundaries │ + │ __spin_t │ Server-side seconds-epoch timestamp │ Every time-based panel │ + │ __spin_b │ Bundle branch (trunk, etc.) │ 09 rev context │ + │ __spin_r │ Spin revision │ 09 rev context │ + │ __hs / __hsi │ Handshake / hash │ Session-integrity signal │ + │ __ccg │ Connection-quality code (GOOD/MODERATE/POOR/BAD) │ CCG overlay on edges │ + │ __comet_req │ Comet internal request sub-seq │ Dedup │ + │ __url_path │ Which endpoint (/api/graphql/, /ajax/…) │ Fallback classifier │ + │ __tab_id (v2) │ Random per-browser-tab id │ Cross-tab dedup │ + │ __latency_ms │ fetch/XHR response latency (v2) │ 12 anomaly scoring │ + │ __response_size │ Response bytes (v2, best-effort) │ Anomaly + size analysis │ + │ __relay_seq (v2)│ Monotonic relay-side broadcast id │ Drop detection │ + └─────────────────┴──────────────────────────────────────────────────┴──────────────────────────┘
Activity-context fields (the semantic layer)
+ ┌──────────────────────────────────┬─────────────────────────────────────────────────────────────┐ + │ FIELD │ PURPOSE │ + ├──────────────────────────────────┼─────────────────────────────────────────────────────────────┤ + │ fb_api_req_friendly_name │ Operation name (e.g. CometUFIFeedbackLikeMutation) │ + │ │ — THE key field: drives category, label, icon │ + │ fb_api_caller_class │ Which caller class issued the request │ + │ doc_id │ Persisted GraphQL query id (stable across user sessions, │ + │ │ churns when FB deploys new bundles) │ + │ server_timestamps │ If present, indicates FB-tagged timings │ + └──────────────────────────────────┴─────────────────────────────────────────────────────────────┘
What is NEVER captured
NO post contents · NO message bodies · NO target user IDs (other than your own __user) · NO response bodies · NO photo data · NO friend/follower graphs · NO third-party data.
GraphQL variables are captured but hard-capped at 300 chars to prevent accidental extraction of payload content. The variables are treated as opaque entropy for segmentation, never parsed for identifiers.
Redacted-at-rest tokens
These are captured in the raw NDJSON because they're bound inside request bodies, but they're masked from the visualizer's detail view by default and stripped to a per-install salt when ingested into the longitudinal SQLite store:
fb_dtsg jazoest lsd __user __s
Sensor events (userscript v2.0 only)
In addition to request blobs, the v2.0 userscript emits low-rate behavioral sensors as separate events with __kind != 'request'. These are useful for survival analysis and for correlating activity with physical attention.
+ ┌───────────────────┬────────────────────────────────────────────────────────────────────────┐
+ │ __kind=input │ Scroll-velocity sampler. Emits when scroll delta > 10px in a 1s window.│
+ │ │ Fields: __delta_px, __velocity_px_per_s, __ts_wall │
+ ├───────────────────┼────────────────────────────────────────────────────────────────────────┤
+ │ __kind=visibility │ Tab gain/loss of focus. Fields: __event ∈ {visible, hidden} │
+ │ │ Tells you when the session actually ended (vs just idled). │
+ └───────────────────┴────────────────────────────────────────────────────────────────────────┘
The 13 analysis panels
Every Reflex HTML report is a single self-contained file with 13 sections. Each section does one thing end-to-end: an analysis function over your events returns a dict, a renderer turns that dict into an SVG, and an auto-generated OBSERVATION box translates the numbers into plain English. The descriptions below are verbatim from the rendered report.
+ ┌─────────────────────────────────────────────────────────────────────────────────────────────┐ + │ # PANEL WHAT IT SHOWS │ + ├─────────────────────────────────────────────────────────────────────────────────────────────┤ + │ 01 ACTION — RESPONSE ENVELOPE The algorithm's reply around each tap. │ + │ 02 SURFACE TRANSITION MATRIX Muscle-memory routes between Facebook surfaces. │ + │ 03 SESSION RHYTHM Activity cadence + MAD-based burst markers. │ + │ 04 HOOK MOMENTS Categories over-represented before you act (p<0.01). │ + │ 05 DIURNAL PORTRAIT 24-hour clock, colored by category. │ + │ 06 PREDICTION ENGINE Markov crystal ball + 5-fold time-series CV. │ + │ 07 HOVER — ACTION CONVERSION Does a mouseover predict the click? (new in 0.2) │ + │ 08 AD DOSE — RESPONSE Cumulative action prob after each ad. (new) │ + │ 09 REVISION BOUNDARIES Facebook's mid-session deploys as free A/B tests.(new)│ + │ 10 TIME-TO-ACTION SURVIVAL Kaplan-Meier of view → action. (new) │ + │ 11 SELF-SURVEILLANCE META-LAYER FB's own telemetry of you. (new) │ + │ 12 ANOMALY SURFACING Top-10 unusual moments in the session. (new) │ + │ 13 DECODER SELF-DIAGNOSTIC How much of your capture Reflex actually understood. │ + └─────────────────────────────────────────────────────────────────────────────────────────────┘
Panel details
01 · ACTION — RESPONSE ENVELOPE For every action you took — a like, a comment, a message, a share — Reflex averages the activity density in the 30 requests before and 30 requests after the moment you tapped. The shape of the resulting curve is the algorithm's reply, averaged into visibility. A spike on the right means the system reacted. A flat line means it didn't — or at least not in a way your own requests could see.
02 · SURFACE TRANSITION MATRIX The probability that going to any surface next given your current surface. Rows are your current location, columns are where you go next. A strong diagonal means you tend to stay where you are — deep scrolling. Bright off-diagonal cells reveal your habitual paths: the routes your attention takes without you ever consciously choosing.
03 · SESSION RHYTHM Activity density over time, bucketed by minute, stacked by category. Pink dots mark burst moments — buckets where activity exceeds the session's MAD-based threshold (modified z-score > 3.5, robust to sparse sessions). The dominant period is recovered via a Welch periodogram, not a greedy local-max search. System categories (presence, self_surv, nav) are rendered faintly so they don't drown out your own behavior.
04 · HOOK MOMENTS For each action you took, Reflex looks at the 5 events immediately before and measures which categories appear more often than their session-wide baseline. Lift values above 1.0× mean that category disproportionately precedes your taps — it's what gets you to act. v0.2.0 adds a permutation test (500 shuffles) and prints p-values with * ** *** significance marks.
05 · DIURNAL PORTRAIT Your activity wrapped around a 24-hour clock, with each hour's petals colored by activity category. Length of a petal = total events in that hour. This is your temporal signature: the hours you belong to yourself, and the hours you belong to the feed.
06 · PREDICTION ENGINE Reflex splits your session 80/20, trains a Laplace-smoothed Markov model on the earlier chunk, and predicts each next event in the later chunk. The time-series k-fold CV underneath (5 folds over temporal segments) catches distribution shift a single 80/20 split misses. The crystal ball applies the fully-trained model to your very last event: if you kept going right now, what would you probably do?
07 · HOVER — ACTION CONVERSION (new in v0.2.0) A mouse hover fires a Hovercard request before any click. This panel measures P(action within 10 events | hover at 0) against a random-event baseline. High lift means your hovers are fire-ahead signals — the algorithm, and Reflex, could predict your taps one intention ahead of them. Hover is the proto-commitment; the hand has decided before the click.
08 · AD DOSE — RESPONSE (new in v0.2.0) Every InstreamAds / AdsHalo pre-fetch marks an ad reaching your viewport. This panel plots P(action at offset k | ad at 0) as a Kaplan-Meier-style cumulative, against a random-event baseline. The gap between the curves is the measurable behavioral effect of ad exposure, on you, with no API access, no survey, no panel — just your own request stream.
09 · REVISION BOUNDARIES — FREE A/B TESTS (new in v0.2.0) A __rev change mid-session is Facebook shipping new code to you mid-scroll. Reflex catches each boundary, compares the 50 events before and after (category mix, request rate), and shows the delta. Over days or weeks, these are the richest natural experiments available to anyone who isn't at Meta.
10 · TIME-TO-ACTION SURVIVAL (new in v0.2.0) Kaplan-Meier-style survival curve: at each viewing event, the clock starts. P(you haven't acted yet) plotted against elapsed seconds. A sharp early drop = you're quick to engage. A long flat tail = you graze, you don't tap.
11 · SELF-SURVEILLANCE META-LAYER (new in v0.2.0) Facebook instruments itself too. FBScreenTimeLogger_syncMutation, TimeLimitsEnforcementQuery, RecordProductUsageMutation, UnifiedVideoSeenStateMutation — these fire while you browse. They're Facebook's telemetry of you, captured here from inside your own browser. This panel shows the cadence of their sampling — the ghost's own signal.
12 · ANOMALY SURFACING (new in v0.2.0) Per-event anomaly score built from four features: log-gap to previous event, same-route streak length, category-transition surprisal, and latency deviation. The top 10 are surfaced — often these are the moments you'd actually remember from the session.
13 · DECODER SELF-DIAGNOSTIC (new in v0.2.0) How much of your capture did Reflex actually understand? The v0.2.0 decoder has 118 patterns covering the full Comet / MAW / BizKit / LSPlatform surface; the ring shows match rate on YOUR data specifically. Top unmatched names are listed — feed them to reflex_discover.py and they become new patterns.
What you see in the report header
Every report.html opens with a cyberpunk observatory header:
+ ╔════════════════════════════════════════════════════════════════╗ + ║ R E F L E X ║ + ║ MIRROR ANALYSIS · SESSION PORTRAIT ║ + ║ generated 2026-04-18 15:29 UTC · span 11:51→11:51 UTC ║ + ║ events 1979 · surfaces 10 ║ + ╚════════════════════════════════════════════════════════════════╝
…followed by a six-card stat strip:
| total events | actions taken | hover intents | ad impressions | session segments | observed span |
|---|---|---|---|---|---|
1979 | 54 | 131 | 41 | 10 | 8h 53m |
Taxonomy — 12 categories
Every decoded event lands in exactly one of 12 categories. v0.2.0 went from 6 → 12 — the new six (hover, ad, presence, react, notif_seen, self_surv) unlock analyses that simply weren't possible before.
User-behavior categories (the signal)
+ ┌────────────┬──────────┬────────────────────────────────────────────────────────────────────┐ + │ CATEGORY │ COLOR │ EXAMPLE OPERATION NAMES │ + ├────────────┼──────────┼────────────────────────────────────────────────────────────────────┤ + │ view │ #7ad7ff │ CometNewsFeedPaginationQuery, HomePageTimelineFeed, StoriesTray │ + │ hover │ #c3f0ff │ CometHovercardQueryRenderer, UserHovercard, Tooltip content (NEW) │ + │ react │ #ff7fbd │ UFIFeedbackLike, UFIFeedbackReact, UFIRemoveReact (NEW — split │ + │ │ │ off from `act` so we can tell likes from actual network actions) │ + │ act │ #ff4fd8 │ SharePost, FriendRequestSend, BlockUser, Save, Hide, Report │ + │ msg │ #ffb347 │ SendMessageMutation, MessengerThread, E2EEBackup, Lightspeed │ + │ compose │ #fff275 │ CommentCreate, PublishPost, StoriesComposer, Composer.Upload │ + │ search │ #9d7bff │ SearchResults, KeywordsDataSource, MarketplaceSearch │ + └────────────┴──────────┴────────────────────────────────────────────────────────────────────┘
System categories (the substrate)
+ ┌────────────┬──────────┬────────────────────────────────────────────────────────────────────┐ + │ CATEGORY │ COLOR │ EXAMPLE OPERATION NAMES │ + ├────────────┼──────────┼────────────────────────────────────────────────────────────────────┤ + │ nav │ #66ff99 │ RouteDefinitions, NavBar, PageQuery, WebStorage, Gating │ + │ ad │ #ff9f40 │ useInstreamAdsHaloFetcherQuery, AdsBatch, SponsoredContent (NEW) │ + │ presence │ #5a8ca0 │ UpdateUserLastActive, PresencePing, HeartbeatMutation (NEW) │ + │ notif_seen │ #b8d4e0 │ NotificationsUpdateSeenState, BadgeCountClear (NEW) │ + │ self_surv │ #8c5a9a │ FBScreenTimeLogger_syncMutation, FBYRPTimeLimitsEnforcement, │ + │ │ │ RecordProductUsageMutation (NEW — Facebook's own telemetry) │ + └────────────┴──────────┴────────────────────────────────────────────────────────────────────┘
Why split system categories out? They're noise if you count them as "you," but they're extremely informative when analyzed on their own. presence pings reveal when Facebook thinks you're active. notif_seen marks the moments of awareness without action. ad gives us a dose-response curve. self_surv is the ghost's own signal — Facebook's telemetry of you, captured from inside your browser.
Statistical rigor
v0.2.0 replaced a handful of ad-hoc thresholds with principled statistics. Every number in the report now comes with a defensible derivation.
+ ┌───────────────────┬──────────────────────────────────┬──────────────────────────────────┐ + │ WHERE │ OLD (v0.1.x) │ NEW (v0.2.0) │ + ├───────────────────┼──────────────────────────────────┼──────────────────────────────────┤ + │ Burst detection │ μ + 2σ over nonzero buckets │ Modified z-score via MAD (k=3.5) │ + │ │ — biased on sparse sessions │ — robust to sparsity + outliers │ + ├───────────────────┼──────────────────────────────────┼──────────────────────────────────┤ + │ Dominant period │ Greedy first local max of │ Welch-style periodogram with │ + │ │ autocorr > 0.15 │ detrending + noise-floor thresh. │ + ├───────────────────┼──────────────────────────────────┼──────────────────────────────────┤ + │ Markov smoothing │ None (zero probability for │ Laplace (add-α, α=0.5) — every │ + │ │ unseen transitions) │ state in vocab gets nonzero mass │ + ├───────────────────┼──────────────────────────────────┼──────────────────────────────────┤ + │ Model validation │ Single 80/20 split │ 80/20 plus 5-fold time-series CV │ + │ │ │ (mean ± std across folds) │ + ├───────────────────┼──────────────────────────────────┼──────────────────────────────────┤ + │ Hook significance │ None (lift ≥ 1.15 → "hook") │ Permutation test (500 shuffles) │ + │ │ │ → p-value + ` * ** *** ` marks │ + ├───────────────────┼──────────────────────────────────┼──────────────────────────────────┤ + │ Session boundary │ Whole file = one session │ Gap > 10 min or __user change │ + │ │ │ → new segment, tagged per-event │ + └───────────────────┴──────────────────────────────────┴──────────────────────────────────┘
What survives the upgrade
- The single-HTML-file output — still no JavaScript framework, still
double-click to open. - Stdlib only — no
pip install, novirtualenv, norequirements.txt. - Pure SVG rendering — responsive, printable, accessible.
- Zero network calls — verified by the absence of any
http:///fetch(/XMLHttpRequestoutside the localhost relay allowlist.
Quick start (five minutes)
You need Python (3.9+), a Chromium-family browser, and the free Tampermonkey extension.
1 — Start the relay with a capture file
cd Session_Cosmos python cosmos_relay.py --log session.ndjson
You'll see a neon banner and three confirmation lines:
✎ NDJSON capture enabled → session.ndjson (appending; 0 existing line(s)) WebSocket server listening on 127.0.0.1:8765 HTTP ingest listening on 127.0.0.1:8766 Ready. Open session_cosmos.html and click CONNECT.
Sanity check from any other terminal:
curl http://127.0.0.1:8766/health
# {"status":"ok","clients":0,"version":"2.0","ports":{"ws":8765,"http":8766},
# "log":{"enabled":true,"path":"session.ndjson","written":0},
# "kinds":{"request":0,"input":0,"visibility":0,"other":0},"broadcast_seq":0}
2 — Open the visualizer (or the live portrait)
Option A — 3D cosmos: double-click Session_Cosmos/session_cosmos.html. It opens in your default browser with a pre-built synthetic cosmos so the scene isn't empty on first launch.
On the right panel, click the STREAM tab, confirm the URL reads ws://localhost:8765, click CONNECT. The status pip should go cyan and read STREAM LIVE. The relay terminal will log the new client.
Option B — live mirror: double-click Reflex/reflex_live.html. New in v0.2.0. Same WebSocket stream, but instead of 3D nodes you get all the Reflex analyses (rhythm, mix, hooks, crystal ball, coverage ring) updating every 750 ms as you browse. Open both at once and keep them side-by-side.
3 — Install the userscript
- Click the Tampermonkey toolbar icon → Create a new script.
- Delete the template. Paste the full contents of
facebook-interceptor.user_2.js(v2.0 — supercharged with latency capture, offline buffer, sensors, multi-site support). If you prefer the leaner v1.1, usefacebook-interceptor.user_1.js. - Save with Ctrl/Cmd+S. Make sure it shows as enabled in the Tampermonkey dashboard.
Visit facebook.com. A neon HUD appears top-right — that's the interceptor. Scroll. Click between Home, Profile, Messenger, Marketplace. You'll see captured / streamed / buffered / errors counters climb and the last activity line update with friendly names like "Scrolling feed" or "Viewing a profile".
Switch back to the Session Cosmos tab — new nodes spawn in real time. The relay terminal logs every blob. session.ndjson grows on disk.
4 — Run Reflex on what you captured
cd ../Reflex python reflex.py report ../Session_Cosmos/session.ndjson -o my_report.html --title "my session" open my_report.html # macOS xdg-open my_report.html # Linux start my_report.html # Windows
If you haven't captured anything yet, use the bundled 800-event synthetic sample:
python reflex.py sample_session.ndjson -o report.html
Architecture
┌────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ Facebook tab │ │ cosmos_relay │ │ session_cosmos │ │ (Tampermonkey │──POST─▶│ .py │──WS──▶│ .html │ │ interceptor) │ │ │ │ (3D viewer) │ └────────────────┘ └──────┬───────┘ └─────────────────┘ hooks fetch / XHR / │ optional sendBeacon, extracts │ --log blob fields + ▼ fb_api_req_friendly_name session.ndjson │ ▼ ┌───────────────┐ ┌─────────────────┐ │ reflex.py │──────▶│ report.html │ │ (offline, 6 │ │ (one HTML file) │ │ analyses) │ │ │ └───────────────┘ └─────────────────┘
The two tools are decoupled through a single file format: NDJSON, one captured blob per line. You can run Session Cosmos without ever touching Reflex, and you can run Reflex on anyone's NDJSON (including your own from months ago) without the relay needing to be up.
Jack Leighton


