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:

Pure Python stdlib backend. Zero build step frontend. Nothing leaves your machine. No calls home.

image

▓▒░ 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


  1. What you get
  2. What it reveals
  3. $\color{#ff4fd8}{\textsf{What data we capture}}$ ◀ new in v0.2.0
  4. $\color{#ff4fd8}{\textsf{The 13 analysis panels}}$ ◀ new in v0.2.0
  5. $\color{#ff4fd8}{\textsf{Taxonomy — 12 categories}}$ ◀ new in v0.2.0
  6. $\color{#ff4fd8}{\textsf{Statistical rigor}}$ ◀ new in v0.2.0
  7. Quick start
  8. Architecture
  9. Session Cosmos in depth
  10. Reflex in depth
  11. Repository layout
  12. Privacy and ethics
  13. Troubleshooting
  14. Platform notes
  15. Roadmap
  16. Contributing
  17. License

What you get


image

ComponentWhat it doesRuns inInputsOutputs
cosmos_relay.py v2.0WebSocket broadcaster + HTTP ingest + NDJSON recorder + multi-kind event routing + monotonic __relay_seq stampA terminal (Python 3.7+)HTTP POSTs from the userscriptLive WS stream + session.ndjson
session_cosmos.html3D visualizer (Three.js, single file, no build step)Your browserWS stream from the relayA flyable cosmos
facebook-interceptor.user_1.jsUserscript v1.1 — hooks fetch / XHR / sendBeacon, extracts blob fields and activity namesTampermonkeyYour browsingPOST requests to 127.0.0.1:8766/ingest
facebook-interceptor.user_2.js v2.0Supercharged — adds response latency, localStorage buffer (survives relay restarts), scroll-velocity sampler, visibilitychange sensor, per-tab id, @match for Instagram + ThreadsTampermonkeyYour browsingRequest + 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 newChar-trigram Jaccard clustering of unmatched friendly_names → auto-suggests regexes + categories + iconsA terminalNDJSON file(s)Python snippet ready to paste into ACTIVITY_PATTERNS
reflex_live.html newReal-time portrait: rolling-window analyses re-computed every 750 ms from the live WebSocket streamYour browserWS stream from the relayLive mirror in the browser
generate_sample.pyDeterministic synthetic session generatorA terminalSeed + event countSynthetic 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:

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.

image

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 (presenceself_survnav) 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_syncMutationTimeLimitsEnforcementQueryRecordProductUsageMutationUnifiedVideoSeenStateMutation — 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 eventsactions takenhover intentsad impressionssession segmentsobserved span
19795413141108h 53m

Taxonomy — 12 categories


Every decoded event lands in exactly one of 12 categories. v0.2.0 went from 6 → 12 — the new six (hoveradpresencereactnotif_seenself_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


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


  1. Click the Tampermonkey toolbar icon → Create a new script.
  2. 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, use facebook-interceptor.user_1.js.
  3. 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

image

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.