hdev avatar

HDev Team HAF Updates - Waggle & HAF FYP: A For You Feed for Hive

hdev

Published: 13 Jul 2026 › Updated: 13 Jul 2026HDev Team HAF Updates - Waggle & HAF FYP: A For You Feed for Hive

HDev Team HAF Updates - Waggle & HAF FYP: A For You Feed for Hive

This is the second post in our development-update series. The prior post covered our core-project analytics work. Today is about Waggle — an algorithmic "For You" discovery layer for Hive, built as an open HAF app, plus the app we built on top of it so you can actually feel what it does.

Hive has never had a personalized feed. Trending and Hot are the same for everyone, tag feeds are manual, and a brand-new account lands on an empty feed with no idea where to go. Every mainstream social app answers this with a per-user ranking layer — a feed that differs depending on who's looking. Hive didn't have one. So we built it.

(A note on the name: a bee's waggle dance is how the hive tells everyone else where the good stuff is. That's the whole product in one word.)

To be clear up front: this is an addition to Trending / Hot / New, not a replacement for them — a fourth, personalized lens that any Hive front-end can adopt. It's early, it's running on one node so far, and we're still tuning it. More on all of that below.

The work in this post:

  • haf_fyp — the open-source HAF app that does the ranking. This is Waggle's engine, and it's usable by any Hive front-end, not just ours.
  • Waggle — a TikTok-style vertical feed app built directly on that API. (Live: hdev-core.github.io/waggle)
  • HivePulse — our browser extension, which now carries a ✨ For You tab alongside trending/tag/new, powered by the same API. The first proof that this drops into an existing front-end.

image.png


The short version

Waggle watches what you already do on-chain — the posts you upvote, the ones you reblog, the communities you subscribe to — and uses that to rank fresh content for you. No tracking pixels, no ad profile, no off-chain surveillance: the input is your public Hive history, and nothing else.

Two things make it different from a normal recommendation feed:

  • It's open, and it's inspectable. The ranking code is public, the weights are published in the API, and every post in your feed can tell you why it's there — tap "Why this post" and you get the actual per-signal scores that produced it.
  • It plugs into Hive rather than replacing it. The feed endpoints return posts in the exact shape Hivemind's bridge API already returns, so any existing Hive front-end can render a Waggle feed with essentially no work.

This is a fourth sort, not a replacement

Hive gives you Trending, Hot and New. Those are global orderings — everyone sees the same list, and that's a feature: they're transparent, predictable, and stake-legible. We're not proposing to replace any of them.

For You is simply a fourth lens, and the only one that differs per person. It's the one Hive has been missing, and it's the one every user coming from a mainstream social app expects to find. Adding it is additive value for the whole ecosystem — and because the API returns the same object shape as bridge.get_ranked_posts, any front-end can add it as one more tab next to Trending/Hot/New.

That's not hypothetical. We've already shipped it in our own HivePulse browser extension, which now carries a ✨ For You tab beside trending/tag/new — personalized when you're signed in, the global ranked feed when you're not, with a short "why you're seeing this" line on each post. It took very little work precisely because of the bridge-shape compatibility. Any other Hive front-end could do the same.

We'd genuinely like other front-ends to pick this up. It's open source, it's a HAF app, and it's designed to be adopted rather than owned.

One caveat we'd rather state early than bury: for now, any front-end adding this would be pointing at our node, since ours is the only one running haf_fyp today. Getting it into the standard haf_api_node stack — so any operator can serve it — is the next step, and we're on it.

Waggle — the personalized For You feed
The For You feed. Note the "For You" chip on the card — every post tells you whether it came from your personalized feed or the global one.

Try it right now (no login, these are live):


How the feed actually decides what you see

This is the part we most want people to understand, so here it is in plain language first.

Every recent post gets a score out of 1. That score is a blend of four signals:

SignalWeightWhat it asks
Relevance40%Is this post about the kind of thing you usually engage with?
Recency30%Is it fresh?
Engagement20%Are other people actually responding to it?
Credibility10%Is the author established and staked in Hive?

Then a community boost is applied on top: a post from a community you're subscribed to (or picked when you signed up) gets a +20% lift; a post from a community you merely engage with a lot gets +10%. A post never gets boosted twice, and the score is capped at 1.

And you can see all of it, for any post, right in the app:

Why you're seeing this — the per-signal score breakdown
Tap "Why this post" on any card. These aren't illustrative numbers — they're the actual scores that produced this ranking, served by the API. This post scored 65 on relevance, 99 on recency, 96 on engagement and 94 on credibility, took the ×1.2 boost because the reader subscribes to that community, and came out at rank #1.

We think an algorithmic feed you can't interrogate is a bad deal. So ours shows its work.

A few things worth spelling out, because they're the interesting bits:

"Relevance" is semantic, not keyword-matching. Waggle doesn't look at tags at all. Instead, every post is turned into a 1,024-dimension vector — a numeric fingerprint of what the post means — using HiveSense, Hive's embedding app. Your own taste is turned into a vector the same way: we take the posts you've recently upvoted and reblogged, and average their fingerprints into a single "this is what you like" vector. Relevance is then simply how closely a post's fingerprint points in the same direction as yours. That's why it can surface a post about, say, urban beekeeping even if it shares no tags with anything you've read — it understands the subject matter rather than string-matching it.

Your taste vector decays. Recent activity counts for more than old activity, and a reblog counts for more than an upvote (you're vouching harder). We look at your last 200 interactions over 90 days, weight each by how recently it happened, and average.

Recency has a 24-hour half-life. A post that's a day old is worth half a brand-new one on that signal; two days, a quarter. This is what stops a permanently-high-scoring "greatest hits" feed from calcifying.

Engagement is deliberately hard to buy. It's built from unique commenters, reblogs and rshares — but each is log-compressed before being combined, so the 500th upvote adds far less than the 5th. A whale self-voting doesn't dominate; a post lots of different people are talking about does.

Credibility is stake- and reputation-aware, at only 10% — enough to damp spam and fresh throwaway accounts, not enough to make the feed an oligarchy.

What if you're brand new and have no history?

This is the empty-feed problem, and it's the one thing a ranking engine can't solve with on-chain history — because you don't have any.

So on first run Waggle shows you a community picker. You pick what you're into, and we synthesize a starting taste vector for you: we take the top-performing posts from the communities you chose, average their fingerprints, and use that as your seed. You get a genuinely personalized feed immediately, not a generic one.

Under the hood, saving your interests pings the ranker directly (over Redis) instead of waiting for the next scheduled pass, so your first real feed lands in seconds. The app polls for it and flips your feed from global to personalized the moment it's ready — you'll see a "Personalizing your feed…" banner, and then it just changes under you.

Until that vector exists, relevance is simply dropped and the remaining three signals are re-weighted to fill the gap — so a cold feed is still ranked sensibly (fresh + well-received + credible), just not yet yours.

And you can always tell which one you're getting: every post in every response carries an fyp.source field, reading either personalized or global. No guessing, no extra API call.

Signing your interests — without giving anything away

Declaring your interests writes to our database, so we have to know it's really you. But we're not going to run passwords, and we're certainly not asking for your keys.

Instead, the app asks your wallet to sign a short message with your posting key. The server recovers the public key from that signature and checks it against your account's real on-chain posting authority. If it matches, the write is accepted.

This is a signature, not a transaction. Nothing is broadcast to the chain, it costs nothing, it burns no Resource Credits, and your private key never leaves your wallet.


Waggle — the app

To make all of that tangible, we built Waggle: a full-screen, vertical, swipe-through feed for Hive.

  • Two tabs — For You and Discover. Discover is the global ranked feed and needs no login at all; For You is yours.
  • Everything plays inline. Images, YouTube (including Shorts), 3Speak, and native video all render right in the card, with shared controls — seek, 1×/1.5×/2× speed, volume — and your preferences carry across every post. Only the post you're actually looking at plays; the rest unmount so your browser isn't quietly buffering ten videos.
  • Every action is a real on-chain Hive operation. Upvote (double-tap the card, or pick a weight), reblog, follow, and comment are all built and signed client-side in your wallet and broadcast to Hive. They're not Waggle actions — they're Hive actions, visible everywhere else on Hive. And they flow straight back into the ranker as fresh signal.
  • Sign in with Hive Keychain or HiveAuth. HiveAuth means mobile works too — QR on desktop, deep-link on your phone. There's also a read-only mode: type your username, get your personalized feed, sign nothing.
  • "Why this post." Tap it on any card for the actual score breakdown — relevance, recency, engagement, credibility, whether a community boost applied, and its final rank. We think an algorithmic feed you can't interrogate is a bad deal, so we made ours explain itself.
  • Sensitive content is gated, not silently dropped. nsfw/nsfl tags and Hivemind's own moderation signals blur the post and disable autoplay behind the blur, with a tap to reveal.
Discover feedFull post reader
Discover — the global ranked feed, no login needed.Reader — the full post, rendered inline, with vote / comment / reblog / follow right there.

Sign in
Sign in with HiveAuth (works on mobile) or Keychain — or skip signing entirely and browse your feed read-only. Your default upvote weight is yours to set.

It installs to your home screen as a PWA, and it ships as static files — there is no Waggle backend. The browser talks straight to the HAF node.

Open Waggle →


Where this actually stands — please read this bit

We'd rather set expectations honestly than oversell a launch.

It only runs on our node right now. Everything in this post is served from testapi.hivescan.info, and ours is currently the only public node running it. haf_fyp isn't yet part of haf_api_node — the standard stack HAF API nodes deploy — so for the moment it isn't something an operator can simply switch on. Getting it there is the natural next step, and we're working on it. Until then: if you build against it today, you're pointing at our infrastructure, and we'd rather say that plainly now than have someone discover it later.

It's a work in progress. Expect rough edges. This is early software shipped deliberately early, because a discovery algorithm is worthless without real users generating real signal. There will be bugs, there will be posts in your feed that make you go "why on earth is that here", and there will be gaps — we've listed several ourselves in the technical section below rather than hiding them. We're actively working on it, and feedback is the whole point: tell us when the feed gets it wrong. That's more useful to us than praise.

The algorithm is not fixed — and it's the node's to choose. The weights live in a database table, versioned and served live at /v1/fyp/algorithm. Any node running haf_fyp can adjust them, or activate an entirely different scoring version, without touching a line of code. What's live today (v1.1.0) is our current experiment, not a pronouncement — we fully expect to tune it, and we may end up running something quite different once we see how it behaves at scale.

That's a deliberate design choice, and worth being clear about: this is not an attempt to install one canonical algorithm at the heart of Hive. Different nodes could run different rankings, and front-ends could offer a choice between them. On a chain whose whole premise is that no single party controls the feed, a pluggable, inspectable, operator-chosen algorithm is the only version of this idea that belongs here.


For the more technical

The stack

hived  sql_serializer  HAF PostgreSQL
                                    
                          haf_fyp_app schema           PL/pgSQL block loop
                          post_candidates · engagement_metrics
                          user_communities · user_interactions
                                    
                          Python ranker (5-min cycle)  Redis: on-demand refresh
                          scores  feed_cache / global_feed_cache
                                    
                          PostgREST  +  FastAPI (writes)
                                    
                          nginx rewriter  clean REST paths

It's a stock HAF app: hived pushes blocks into PostgreSQL via sql_serializer, and a PL/pgSQL loop (hive.app_next_iteration()) processes each block range into the app's own schema — root posts into post_candidates, comments/reblogs/votes into engagement_metrics, community subscribes into user_communities, and each user's votes/reblogs into user_interactions. Fork handling is HAF's problem, not ours.

Scoring runs outside SQL, in a Python daemon, on a 5-minute cycle over a candidate pool of recent + still-in-payout-window posts.

The scoring function

Weights live in an algorithm_versions table and are served live at /v1/fyp/algorithm. Active version is 1.1.0:

final = 0.40·relevance + 0.30·recency + 0.20·engagement + 0.10·credibility

then, multiplicative and capped at 1.0:
  subscribed / declared community   × 1.20
  engaged (top-5) community         × 1.10

The four signals, precisely:

  • relevance — cosine similarity between the user's interest vector and the post's embedding, mapped from [-1,1] to [0,1] as (sim+1)/2. Embeddings are HiveSense's halfvec(1024), averaged across chunks for long posts. No embedding available → neutral 0.5.
  • recencyexp(-ln2 · age_hours / 24). A clean 24-hour half-life.
  • engagementlog1p(unique_commenters) + log1p(reblogs) + log1p(rshares/1e9), pushed through a sigmoid (k=0.5, midpoint 3.0). Note it uses unique commenters, not comment count, and never raw vote count.
  • credibility0.6·norm(reputation) + 0.4·norm(vests), sourced from reptracker and the balance tracker. Unknown author → 0.3.

The interest vector: last 200 interactions within 90 days, each weighted action_weight · 0.95^age_days (vote 1.0, reblog 1.5), averaged over those posts' embeddings and L2-normalised into a vector(1024). Cold start instead averages the embeddings of the top-50 posts by rshares in the user's declared communities. With no vector at all, relevance is zeroed and the remaining weights are renormalised over 0.60 (→ recency 0.5, engagement 0.33, credibility 0.17).

Two honest notes, since we'd rather say them than have you find them: there is currently no feed diversification — no per-author cap, no MMR — ranking is a straight sort by final score. And the similarity is computed in-process with numpy over embeddings fetched by post id; haf_fyp does not run a pgvector ANN index search (the HNSW work lives in HiveSense, a separate app). Both are on the list.

Bridge compatibility, properly

The feed endpoints don't return a bridge-like object. They call Hivemind's own get_full_post_view_by_id() and create_bridge_post_object(), so payout maths, reputation and vote state are never reimplemented and the output stays correct as Hivemind evolves. Waggle's data is merged in as a nested fyp object:

"fyp": {
  "rank": 1, "source": "personalized", "final_score": 1.0,
  "score_relevance": 0.652, "score_recency": 0.989,
  "score_engagement": 0.962, "score_credibility": 0.939,
  "community_boost_applied": true, "boost_source": "subscribed"
}

That's what makes this drop-in for any Hive client: if your front-end already renders bridge.get_ranked_posts, it can render this.

The API surface: GET /v1/fyp/feed/{user}, /v1/fyp/global, /v1/fyp/profile/{user}, /v1/fyp/explain/{user}/{post_id}, /v1/fyp/algorithm, plus a signed POST /v1/fyp/interests. Full OpenAPI spec at the API root.

If you want to add a "For You" tab to your front-end

The minimum viable integration is genuinely small, and HivePulse is the worked example:

  1. Add a sort key next to trending / hot / new.
  2. Point it at GET /v1/fyp/feed/{user} when a user is signed in, and GET /v1/fyp/global when they're not. Today that base URL has to be testapi.hivescan.info — ours is the only node serving haf_fyp. Keep it configurable, so it can point elsewhere the moment another node runs it.
  3. Render the response with the same component you already use for bridge.get_ranked_posts — it's the same object.

That's it. Everything else is optional polish: read the nested fyp object if you want to show why a post surfaced (HivePulse renders a one-line reason from boost_source and the scores), and read fyp.source to tell the user whether they're seeing a personalized feed or the global fallback.

The interest picker (POST /v1/fyp/interests) is only needed if you want to solve cold-start for brand-new accounts; it's the one endpoint that requires signing.

Swapping the algorithm

Scoring weights are rows in an algorithm_versions table, not constants in the source. The active version is served at /v1/fyp/algorithm, and a node operator can activate a different one through an admin endpoint without redeploying anything. Version 1.0.0 (flat community boost) and 1.1.0 (the tiered boost, currently active) both ship seeded.

This is the mechanism behind the claim earlier in the post: the algorithm belongs to whoever runs the node. If you run haf_fyp, the ranking your users get is yours to set — and if you think our weights are wrong, you can simply run different ones and we'd be interested to hear how it goes.

Interest-write auth

Posting-key signature over a canonical challenge string binding username, timestamp and the sorted, de-duplicated community id list (so a captured request can't be replayed with a different body). The server rebuilds the string, recovers the pubkey from the 65-byte graphene compact signature, fetches the account's on-chain posting authority, and requires the recovered key to clear weight_threshold on its own — a single key of an N-of-M multisig is rejected. Signatures expire after 300s. The node URL used for that authority lookup has no default and fails closed: a misconfigured deploy returns 503 rather than trusting an attacker-supplied key.

Known gap, stated in the code: delegated posting authority (account_auths) isn't resolved yet, so signing with a delegated account's key is a false negative.

Performance work

This cycle was mostly about making the feed path fast enough to be a real-time UI. It now serves a 20-post page in ~0.8s end-to-end against mainnet-scale data (~0.44s TTFB), from a cold HTTP client.

  • Precomputed hive_post_id. haf_fyp's post ids are HAF operation ids, not Hivemind ids, so every feed request used to run a 3-way join into Hivemind to bridge the keyspaces. The processor now resolves it once and stores it, turning the feed into a single primary-key join.
  • Cached rendered bridge JSON. The dominant per-request cost was the Hivemind bridge build itself, at ~25–30 ms/post — brutal at 20 posts/page. Those fields are volatile only during the ~7-day payout window and then freeze, so the ranker now pre-renders the observer-agnostic bridge object into post_bridge_cache and endpoints read it, falling back to a live render on a miss.
  • A partial index that gave the whole node back. The per-block payout-window UPDATE was sequentially scanning all of post_candidates25M rows, 17 GB — every ~3 seconds, which came to roughly 98% of the app's per-block CPU and was starving the other HAF apps on the same box. A partial index on (created_at) WHERE payout_window turned it into a sub-millisecond range scan. Node load dropped from ~5.7 to ~1.6, and the block explorer stopped throwing 503s.
  • Page size capped at 20, so enrichment cost is bounded per request. (Pagination uses page/page-size, not limit/offset — PostgREST reserves those.)
  • Graceful degradation throughout: if HiveSense or the credibility apps are unreachable, the ranker falls back to neutral relevance/credibility and keeps producing feeds rather than failing the cycle.

The app

React + Vite + TypeScript, no backend and no BFF — the browser calls PostgREST directly (the node already sends Access-Control-Allow-Origin: *). State is TanStack Query; the pager is CSS scroll-snap rather than JS; two IntersectionObservers separate mount/preload (~1.2 screens out) from play (centred), so video is prefetched but only ever one plays.

Ships with 39 unit tests plus 6 live contract tests — the latter run against the real API and assert the cold-start shape (score_relevance nullable, unknown user still returns has_vector: false) and that a real 3Speak manifest is fetchable and CORS-open. Those exist because every one of the upstream surprises above bit us at least once.

The code

15 MRs merged this cycle on haf_fyp (26 total). It runs on the same self-hosted node as everything else in this series (testapi.hivescan.info), alongside Hivemind, HAF Block Explorer, Balance Tracker and haf_stats — it composes with the stack rather than sitting beside it. The Waggle app deploys from main to GitHub Pages on push.


What we're asking for

Try it and tell us when it's wrong. A ranking algorithm can't be evaluated in a vacuum — it needs real people with real taste telling us the feed missed. Bug reports and "this recommendation made no sense" are both genuinely useful.

Front-end devs: you're welcome to try it — but know what you'd be depending on. Adding a For You tab is a small change (see above), and the code is open source. The one honest catch: ours is currently the only node serving it, so today that tab would point at testapi.hivescan.info — our infrastructure, our uptime, our algorithm version. Keep the base URL configurable and it moves the moment that changes. Getting haf_fyp into the standard haf_api_node stack is the natural next step, and it's something we're working on.

Join our discord


Next up in the series: the new developer cohort we've brought onto Hive this cycle — 15 engineers and the apps they're building — plus a spotlight on Vylor, an AI voice-first finance app coming to Hive. We'll share it in a couple of days.

HDev Team

Leave HDev Team HAF Updates - Waggle & HAF FYP: A For You Feed for Hive to:

Written by

Read more #hive-139531 posts


Best Posts From hdev

We have not curated any of hdev's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.

More Posts From hdev