SemiLayerDocs

Feeds

search ranks rows by "closest to my query." query returns exact matches from your source. Feeds are different again: a declarative ranking primitive that turns a lens into a paginated, optionally live-updating stream. Each feed is declared under feeds.<name> and granted under grants.feed.<name>.

One config block becomes:

  • A typed Beam method — beam.<lens>.feed.<name>(params) returns one page
  • A WebSocket subscription — .subscribe() yields feed.tick events
  • A debug endpoint — .explain({ recordId }) returns per-scorer math (sk-only)
  • A React hook — useFeed(handle, { context, liveUpdates })
feedrecipes
recipes: {
  source: 'main',
  table: 'recipes',
  fields: { /* see Search docs */ },
  feeds: {
    discover: {
      candidates: { from: 'embeddings', limit: 500 },
      rank: {
        similarity: { weight: 0.6, against: 'liked_titles' },
        recency:    { weight: 0.1, halfLife: '7d' },
        engagement: {
          weight: 0.3,
          lens: 'recipe_likes',
          relation: 'likes',
          aggregate: 'recent_count',
          window: '24h',
          decay: 'log',
        },
        diversify: { by: 'cuisine', maxPerGroup: 3 },
      },
      pagination: { pageSize: 12, dedup: { by: 'sourceRowId', within: 'session' } },
    },
  },
  grants: {
    feed: { discover: 'public' },
  },
}
ℹ️

See feeds in action at demo.semilayer.com/feeds — three feeds on one lens (discover / latest / related), rendered with useFeed and live WS ticks.

Three patterns from one primitive

A single lens can host as many named feeds as you want. The canonical shapes:

  1. Discover — algorithmic. similarity to user context + recency + engagement. Powers the "for you" tab.
  2. Latest — chronological. recency-only. Powers the "newest" tab. candidates: { from: 'recent' }.
  3. Relatedsimilarity.against: { mode: 'recordVector' }. The seed is a single clicked record's existing vector. Powers "more like this." Zero embedding API cost.

One lens, one set of vectors, three named feeds — each gets its own typed method, access rule, pagination, and scorer mix.

The building blocks

Every feed has the same structure:

<feedName>: {
  candidates: { from, limit?, where? },     // what rows to consider
  rank:       { similarity?, recency?,      // how to score them
                engagement?, diversify?,
                boost? },
  pagination: { pageSize?, cursor?, dedup?, // how pages compose
                seenWindow?, excludeIds? },
  evolve?:    { onSubscribe?, pollOnIngest, // when to tick
                minNotifyInterval? },
  fields?:    string[],                     // shape the response
}

Every field is typed in @semilayer/core's FeedConfig — the Beam codegen produces per-feed methods with full inference.

Access rules

Feed access is per-named-feed. Add to grants.feed:

grants: {
  feed: {
    discover:  'public',
    latest:    'public',
    relatedTo: 'public',
    admin_queue: 'staff',   // claim-check rule
  },
}

Feeds without an explicit grant default to deny for pk_ keys. sk_ keys bypass. No accidental exposure.

Where to start

  • Quickstart — add a feed, push, render with useFeed in under 5 minutes.
  • Ranking — every scorer with worked math and valid tokens.
  • Signals (your data) — engagement reads through a sibling lens, personalization context.
  • Related items — the recordVector pattern in ~12 lines.
  • Live evolution — WS subscribe + the feed.tick contract.
  • Pagination & dedup — cursor, seenWindow, within modes.
  • Explain — per-scorer math for "why is this ranked here?"
  • Caching — BYO FeedCache with five built-in adapters.
  • Recipes — short configs for common feed types.