SemiLayerDocs

Feeds — Recipes

Six working patterns you can copy. Each is a complete FeedFacetConfig — drop it under facets.feed in a lens config, add a matching rules.feed entry, push, done.

News — recency-heavy

"Freshest stuff first, similarity as a tiebreaker."

feedarticles
latest: {
  candidates: { from: 'recent', limit: 100 },
  rank: {
    recency:    { weight: 0.8, halfLife: '1h', field: 'published_at' },
    similarity: { weight: 0.2, against: 'topics' },
  },
  pagination: { pageSize: 20, dedup: { by: 'sourceRowId' } },
}

Discover — similarity + engagement + diversify

The classic "for you" feed. Similarity to user context, popularity boost, diversified by cuisine so one hot category doesn't eat the page.

feedrecipes
discover: {
  candidates: { from: 'embeddings', limit: 500 },
  rank: {
    similarity: { weight: 0.6, against: 'liked_titles' },
    engagement: {
      weight: 0.3,
      lens: 'recipe_likes',
      relation: 'likes',
      aggregate: 'recent_count',
      window: '24h',
      decay: 'log',
    },
    recency:   { weight: 0.1, halfLife: '7d' },
    diversify: { by: 'cuisine', maxPerGroup: 3 },
  },
  pagination: { pageSize: 12, dedup: { by: 'sourceRowId', within: 'session' } },
}

"What's hot right now." Engagement dominates; recency keeps the window tight.

feedposts
trending: {
  candidates: { from: 'recent', limit: 300 },
  rank: {
    engagement: {
      weight: 0.7,
      lens: 'post_likes',
      relation: 'likes',
      aggregate: 'recent_count',
      window: '1h',
      decay: 'log',
    },
    recency:   { weight: 0.3, halfLife: '6h', field: 'posted_at' },
    diversify: { by: 'author_id', maxPerGroup: 2 },
  },
  pagination: { pageSize: 20 },
}

"Things like this one." Zero embedding-API cost per click — uses the seed record's stored vector directly.

feedrecipes
relatedTo: {
  candidates: { from: 'embeddings', limit: 200 },
  rank: {
    similarity: {
      weight: 0.75,
      against: { from: 'context.seedRecordId', mode: 'recordVector' },
    },
    engagement: {
      weight: 0.2,
      lens: 'recipe_likes',
      relation: 'likes',
      aggregate: 'count',
    },
    diversify: { by: 'cuisine', maxPerGroup: 2 },
  },
  pagination: {
    pageSize: 6,
    dedup: { by: 'sourceRowId' },
    excludeIds: 'context.seedRecordId',
  },
}

Editorial — boost-pinned

A curated feed where the editor promotes specific items. Use boost to pin without hacking scores.

feedarticles
curated: {
  candidates: { from: 'embeddings', limit: 200 },
  rank: {
    similarity: { weight: 0.5, against: 'topics' },
    recency:    { weight: 0.2, halfLife: '7d', field: 'published_at' },
    boost: [
      { when: { featured: true }, add: 0.5 },   // pin featured to the top
    ],
  },
  pagination: { pageSize: 10 },
}

Following — per-user include filter

"Feeds from people I follow." Narrow the candidate pool via where on the candidate selector, then rank by recency + engagement.

feedposts
following: {
  candidates: {
    from: 'recent',
    limit: 300,
    where: { author_id: { $in: 'context.followed_ids' } },  // filter candidates
  },
  rank: {
    recency: { weight: 0.7, halfLife: '6h', field: 'posted_at' },
    engagement: {
      weight: 0.3,
      lens: 'post_likes',
      relation: 'likes',
      aggregate: 'recent_count',
      window: '24h',
    },
  },
  pagination: { pageSize: 20 },
}

Picking weights

A few rules that hold across all of the above:

  • similarity + recency + engagement should sum to ~1.0. Boosts stack on top — treat them as ±0.1 to ±0.5 nudges, not dominating terms.
  • Engagement gets a smaller weight than your intuition wants. Like counts compound; unchecked, the oldest popular content eats the page. Start at 0.2–0.3.
  • Recency half-life matches content rhythm. '1h' for breaking news, '6h' for social timelines, '7d' for editorial, '30d' for evergreen.
  • Diversify if one field dominates the candidate pool. by: 'author_id' for social, by: 'cuisine' for food, by: 'section' for news.

Once you've picked weights, run explain on a few representative records and watch the contributions array. That's your feedback loop.