Docs / distribution.md · mirrored from the repo
Namaste Ji — Content Distribution
Status: design / brainstorm. Nothing built yet — agreed direction, captured so decisions aren’t lost. Living doc.
Distribution is its own plane: it sits downstream of CREATIVE-PLANE.md (catalog → users) and extends the two-plane model in ARCHITECTURE.md. The creative plane fills the catalog; distribution decides what each user sees, when, where, and whether it’s worth sending.
1. Why this is its own beast
Fresh content reaching the right people is the product. First principles specific to our audience (next-billion, Tier-2/3, 35+, Android, vernacular):
- The unit is “a greeting to send,” not “a video to watch.” Success = share-intent (the user forwards it), not watch-time or session length.
- Users want something unique to send — so we deliberately do NOT chase virality. A greeting that everyone else also sent is devalued (if your aunt gets the same Diwali image from 8 people, it’s worthless). This inverts the normal recsys goal — see §3.
- Sessions are a daily ritual, not a doom-scroll. Open in the morning, grab something good, send it, leave. Optimize time-to-a-good-send, not time-on-app.
- Freshness is survival, and variety is the product. People scroll a lot; the feed must feel endless and distinct.
- Two systems, not one. Ranking (what to show) is separate from edge logistics (getting bytes near a low-end/offline-ish user cheaply). Don’t conflate them.
- Local time, local calendar. Event content lands on the user’s local day inside its campaign window (CREATIVE-PLANE §10).
- Branding rides the share-loop. Content spreads peer-to-peer (WhatsApp); the Namaste Ji watermark (CP-9) travels with the image = our growth surface. We let it ride; we do not try to instrument it (§9).
2. Timeless vs time-bound — two pools, two lifecycles
Every asset carries temporal_class (set at creation/ingest):
| Timeless / evergreen | Time-bound | |
|---|---|---|
| Examples | generic Good Morning, roses, generic blessings, weekday↔deity | festival/event (Diwali, regional new year), date-pinned |
| Validity | long shelf life; recyclable | only within its campaign window; region/date-pinned |
| Role in feed | base load — fills the endless scroll, amortizes gen cost | spikes — boosted hard during window, on the local day |
| After use | cooldown → rotate back in | retired/archived at decay_until; maybe revived next year |
| Storage | hot | hot during window → cold/archive after |
This split drives ranking boosts, retirement jobs, storage tiering, and the generation mix a brief targets (how much evergreen vs event).
3. The objective — amplify the recipe, never the dish
The defining principle, and the inversion of standard feed algorithms. Separate two kinds of amplification:
- Distribution amplification (show the same item to more people) — minimized. Convergence is the enemy: uniqueness is the product value. We spread and diversify instead.
- Production amplification (generate more, distinct content in a winning vein) — the goal. A hit means “make more like this,” not “show this to everyone.”
So the share-intent signal is a generative feedback signal, not a distribution-boost signal: we mine winning patterns/attributes, not winning items (§9). At the serve layer this shows up as two non-standard ranking terms:
- Novelty-to-this-user is a first-class objective, co-equal with quality (not a mere diversity constraint).
- Rarity / anti-popularity: the more a piece has already been sent (globally, and especially within a locale / social proximity), the less we surface it — the inverse of a popularity boost.
Per-user serve objective:
high-quality × context-right × novel-to-you × not-over-circulated
with predicted share-propensity as a quality/appeal prior, never a distribution lever.
Feasibility — perceived, not absolute, uniqueness. We can’t make 100M unique images/day and don’t need to. Target: two people who might send to the same recipient rarely send the same image. Achieved with a huge, continuously-refreshed, diverse catalog + spreading distribution + a per-item circulation cap (de-prioritize a piece after N sends in a locale). This also kills the rich-get-richer / monotony trap by design — we never amplify a winner in distribution, so feeds can’t collapse onto a few hits. The content engine, not the ranker, is the moat.
4. The feed algorithm — borrowed machinery, inverted objective
Modern feeds (TikTok, Instagram Reels, YouTube Shorts) all run a multi-stage funnel; we borrow the structure but change the goal (§3). What we take from each:
- TikTok — content-first, graph-free retrieval (recommend with no social graph; we have none either) + exploration laddering (seed new items to a small pool, expand on success). We keep laddering but apply it at the segment level and to learn, not to make an item go viral.
- Instagram — has made sends/shares a North-Star quality signal. That’s literally our primary objective from day one.
- YouTube — candidate-gen + ranking split and the discipline of optimizing the real goal, not a cheap proxy (watch-time/satisfaction over clicks → for us, good sends over raw taps).
Our funnel (computed per user × surface × moment):
flowchart LR
C[Candidate<br/>generation] --> E[Eligibility<br/>filter] --> R[Ranking] --> D[Diversify] --> P[Pace /<br/>rollout] --> X[Deliver]
- Candidate generation — a contextual floor + a behavioral overlay (detailed below). Context pools keyed by time-of-day + today’s calendar (festival/weekday-deity/lunar) + locale/language + declared preferences, ∪ a server-side behavioral-taste neighborhood (multimodal kNN), ∪ a “fresh-unproven” slice, ∪ a small “rare/under-circulated” slice. Context alone is strong enough that cold-start is largely a non-problem — a brand-new user gets a great morning feed before any behavior exists.
- Eligibility — locale/language match, seen-cooldown, retired/quarantined/ over-circulated removed, safety (already gated upstream, so this is light).
- Ranking (heuristic v1 — no ML needed at launch):
score = eval_quality × share_propensity × freshness_decay × event_priority × pref_affinity × taste_affinity × novelty_to_user − circulation_penalty.share_propensitystarts as the item’s (or its attribute-cluster’s) historical segment-level share-intent rate;taste_affinityis the server-side behavioral term (below) that activates only as a user’s profile accrues; tune/learn weights later. - Diversify — MMR / embedding-distance so adjacent items aren’t near-dupes (vary motif, palette, deity, composition).
- Pace / rollout — meter the batch over time (§6); pre-position to edge/device (§7).
- Deliver — feed, daily push, share assets; cache-first.
Compute model — server-side, multimodal-embedding-backed, LLM as the offline brain. No on-device ML (it’s painful on low-end Android and slows iteration); everything runs server-side. The split that keeps this cheap at scale:
- Fast path (per request) = vector math + filters. A per-user (or per-cohort) taste vector retrieves candidates by kNN over a single multimodal (image+text) embedding index (Vectorize), then heuristic scoring + structured filters. No LLM in this loop — that’s what would bankrupt a free next-billion app (one LLM call/user/day at 100M ≈ tens of $M/yr; same shape as the per-MAU auth problem, ARCHITECTURE §5).
- Slow brain (offline, between requests) = LLMs. Batch/cron jobs use LLMs to infer & refresh preference profiles from behavior, tag/cluster content, and curate playlists — then distill the result into embeddings / small rulesets the fast path reads cheaply. Refresh a profile periodically or after N new signals, never per scroll.
- Precompute + cache. Ranked playlists are built server-side (per-cohort → per-user as it grows) and cached/pushed to KV at the edge; the device just renders and applies local cooldown.
MVP note: at launch volume you can be LLM-liberal (even per-user per-session) — it’s the fastest path and cheap at low scale. Just keep the seam between the LLM slow brain and the cheap fast path, so scaling means “move LLM work to offline/cohorts,” not a rewrite.
Behavioral candidate generation — one multimodal space, server-side. Because content, text queries, declared preferences, and the taste vector all live in the same multimodal embedding space, behavioral retrieval is just kNN:
- Unified embedding. A multimodal model (image+text, routed via AI Gateway) embeds every asset at ingest; the user’s taste vector = a recency/share-weighted aggregate of the embeddings of what they engaged with — built/refreshed by the offline brain. The index is queryable by image, text, or both.
- Neighborhood, not item. Behavioral retrieval picks the region of taste; then novelty + rarity + circulation-cap pick a distinct, under-circulated item within it. “Find the right vein; never the same nugget” — so behavioral CG and the no-virality rule (DS-4) coexist.
- Overlay, never a dependency. Context is the floor (cold-start, day one, the big festival content everyone should see); behavioral refines as signal accrues and degrades gracefully to pure context when there’s no profile yet.
The resulting candidate selection function:
candidates = context_pool // floor: time × calendar × locale × declared prefs
⊕ behavioral_neighborhood // server-side taste vector → kNN over the multimodal index
⊕ fresh_unproven_slice // exploration / freshness
⊕ rare_undercirculated // anti-popularity supply
→ eligibility (cooldown, circulation cap, safety)
→ score (above) → diversify (MMR)
5. Cold-start & exploration
New batches have zero engagement signal, and freshness is survival — so every feed mixes proven + fresh-unproven content. Freshness is a first-class ranking term that decays with exposure. Exploration is segment-level (Thompson-sample on share-rate per item per segment — tractable; per-user bandits aren’t, and we lack the data). Its purpose is to learn winning attributes and keep feeds fresh — not to find a hit to blast everyone (§3).
Behavioral signal accrues slowly here (daily-ritual cadence, not 200 interactions/day), so the server-side taste profile (§4) is a slow-burn refinement — densify it with cheap implicit signals (opens, skips, dwell), not just shares. Keep the exploration slice + coverage constraints (§8) live so behavioral personalization can’t harden into a filter bubble: a Shiva-leaning user must still see the festival content meant for everyone (less dangerous for devotional than political content, but still real).
6. Pacing & rollout — a batch is a release schedule, not a publish event
“Published” ≠ “shown to everyone now.” Meter a batch across days to sustain freshness.
For events, release along the campaign window (promote_from → peak → decay_until,
CREATIVE-PLANE §10), peaking on the user’s local day. This is the bridge from
CREATIVE-PLANE §9 publish to the live feed.
7. Edge logistics — pre-positioning (separate from ranking)
Get bytes near the user cheaply: prefetch tomorrow’s morning set overnight on WiFi,
cache-first, strict .nomedia (ARCHITECTURE.md principle 2). A per-locale “what’s live
today” manifest at the edge (KV) + R2 origin + Images variants sized to device/network.
8. Diversity, novelty & repetition control
Variety is the goal, not an afterthought — and here it’s load-bearing because uniqueness is the value (§3). Strict dedup only kills near-identical waste at ingest; repetition is managed at serve time: diversity-aware ranking + per-user seen-cooldown (timeless can recur after cooldown; never the same piece twice in a short window) + per-item circulation caps (so a piece isn’t over-sent within a locale). Guardrail: novelty/rarity alone surfaces rare junk, so they always sit behind the quality floor (eval/Judge gate) and context-fit — “unique” never means “unique but bad or off-occasion.”
9. Feedback loops — two of them, measured lightly and safely
What we measure (deliberately minimal — privacy-light, see CREATIVE-PLANE §14). Only
an in-app share-intent event: the user tapped share/forward on a piece
(content_id, timestamp, locale/theme). The moment the OS share-sheet opens, the
hand-off happens and we stop following — no recipient, no off-platform beacon, no
attribution graph. Kept aggregate (share-tap counters per content / locale / attribute)
wherever a feature doesn’t need per-user history. Plus the cheap implicit signals (open,
skip/scroll-past) and explicit onboarding preferences (languages, deities/festivals).
Because personalization is now server-side, the per-user profile/behavior we do keep
is PII — it lives in the region-pinned PII plane, minimized and retention-bounded
(DS-15, CREATIVE-PLANE §14). On-device gave privacy for free; server-side, we engineer it.
Loop A — serve-time ranking. Share-intent (the strongest positive), open/skip, and cooldown feed the ranking score (§4).
Loop B — generative (the important one). Share-intent aggregated by attribute (style, motif, palette, deity, composition, theme, language) tells the Creative Head which directions resonate → the next brief asks for more, distinct content in those veins (production amplification, §3) — not more distribution of the hit item. Requires assets to be richly attribute-tagged at generation/ingest (CREATIVE-PLANE §9).
What we knowingly give up: the true viral coefficient / off-platform spread. That’s the right trade now — we measure “is this worth sharing,” not “who shared to whom.”
10. Cloudflare mapping
| Concern | Primitive |
|---|---|
| Precomputed playlists / “live today” manifest | KV (edge), rebuilt by a scheduler |
| Asset origin + variants + cache | R2 + Cloudflare Images + Cache |
| Multimodal (image+text) embeddings — content, query, preference, taste, dedup, diversity, catalog analysis (one index) | Vectorize (embedding model via AI Gateway) |
| Per-user taste vector + profile + state (seen, cooldown, prefs) | D1 / Durable Objects — region-pinned PII |
| Fast-path retrieval + ranking + playlist build (no LLM) | Workers |
| Offline “slow brain” — profiling, tagging, clustering, curation, Creative Head | LLMs via AI Gateway (batch/cron) |
| Rollout + prefetch + profile/playlist-rebuild jobs | Cron Triggers + Queues |
| Share-intent + engagement counters | Analytics Engine → feedback store |
11. Decisions log (extends ARCHITECTURE.md D1–D8 / CREATIVE-PLANE.md CP-*)
| # | Decision | Rationale |
|---|---|---|
| DS-1 | Distribution is its own plane/subsystem (catalog → users) | Distinct from creation; large enough to own a doc |
| DS-2 | Optimize time-to-a-good-send, not time-on-app; the unit is a greeting to send | Daily-ritual product, not an engagement trap |
| DS-3 | temporal_class: timeless | time_bound split, different lifecycles | Drives ranking, retirement, storage, generation mix |
| DS-4 | Amplify the recipe, never the dish — share-intent drives production, not item distribution; we don’t chase virality | Users want unique things to send; convergence devalues content |
| DS-5 | Novelty-to-user + rarity/anti-popularity are first-class ranking terms; per-item circulation caps | Inverts the usual popularity boost; kills rich-get-richer by design |
| DS-6 | A batch is a paced release schedule, not an instant publish | Sustains freshness; matches event campaign windows |
| DS-7 | Ranking (what) separate from edge logistics (bytes) | Low-end/offline needs pre-positioning independent of ranking |
| DS-8 | Server-side per-user personalization; precompute + cache playlists; no on-device ML | On-device ML on low-end Android is painful + slow to iterate; server-side is simpler and gives true per-user taste vectors — as long as the per-request path stays cheap (vectors+filters) and LLMs stay offline (DS-14) |
| DS-9 | Coverage constraints protect long-tail locales | Popularity-only ranking starves minority languages |
| DS-10 | Measure share by in-app intent only; OS share-sheet is the privacy line; keep aggregate | Light + safe; avoids off-platform tracking & privacy risk |
| DS-11 | Feed funnel borrows TikTok (content-first + laddering) / IG (sends-as-NSM) / YT (multi-task, optimize real goal) machinery | Proven structure; we change only the objective |
| DS-12 | Behavioral candidate generation via a unified multimodal embedding space, server-side: a per-user taste vector retrieves a taste neighborhood by kNN (taste_affinity term) | One index serves content/query/pref/taste; picks a neighborhood not an item (coexists with DS-4); a context overlay that degrades to context at cold-start |
| DS-13 | One multimodal (image+text) embedding index for content, query, preference, taste, dedup, diversity and catalog analysis | Retrieve by image/text/both; collapses several pipelines into one; same index powers the Creative Head’s catalog gap analysis (CP-16) |
| DS-14 | LLMs are the offline “slow brain” (profiling, tagging, clustering, curation, Creative Head); never in the per-request hot path | Per-request LLM calls bankrupt a free next-billion app; serving stays cheap vector math; keep a seam so scale = move LLM work offline/cohort |
| DS-15 | Server-side behavior/profiles are PII → privacy is engineered (region-pinned PII plane, minimization, retention), not inherited from on-device | Dropping on-device removed the free-privacy benefit; design for DPDP/GDPR instead (CREATIVE-PLANE §14) |
12. Open questions
- Ranking: heuristic v1 vs learned model (all server-side).
- Multimodal embedding model choice + sourcing (Workers AI vs external via AI Gateway) — verify current CF options.
- Per-cohort vs per-user profiles at scale — where the cost/quality line sits.
- PII minimization + retention policy for server-side profiles; how much profile is enough.
- Exploration policy (bandit flavor) + size of the exploration budget.
- Circulation-cap tuning: N sends per locale before de-prioritizing; window length.
- Cross-year revival of time-bound content (reuse last year’s Diwali assets vs regenerate).
- Whether off-platform virality ever needs measuring (deep links) — parked, likely not.
13. MVP cut line
In: timeless pool · multimodal embedding index (retrieval + dedup + catalog gap analysis) · heuristic ranking (eval-quality × recency/freshness × novelty/cooldown) · per-user seen-cooldown · daily Good-Morning delivery · server-side precomputed playlists (per-cohort) + edge cache · basic time-bound boost during seeded events’ campaign windows · LLM-driven catalog/strategy analysis for the Creative Head (offline) · share-intent logging (aggregate; consume later) · explicit onboarding preferences.
Out (later): deep per-user behavioral profiling · bandit exploration · attribute-level generative feedback loop (Loop B) automation · per-item circulation caps · learned ranker · cross-year revival · advanced coverage constraints · any off-platform attribution.