how it works
a feed usually ranks by your history, everything you ever clicked. this one ranks by your taste. pick a few posts and the page averages them. that average is all it knows about you, and it lives in the link
manicule.pyrank()same math in rank.js
two averages and a subtraction
tullie murrell's hn for-you feed, cosine to the mean of your favorites
a post is an arrow
a small language model reads each post's headline and the first 400 characters of its blurb and writes down 384 numbers. that runs once, when the page is built. the model never sees you
think of the 384 numbers as an arrow. posts about the same thing point the same way. to compare two posts you measure the angle between their arrows, and the cosine of that angle is the number you see all over this site. +1 is the same direction, 0 is a right angle, below 0 is opposite
| against the top post on your source | source | cos |
|---|---|---|
| loading | ||
the three nearest and the three farthest
on this feed almost every pair lands between +0.27 and +0.75. so +0.62 is close and +0.40 is far
your taste is an average
pick a post and the page keeps its arrow. pick another and it averages the two. that average is your taste. no profile, no history, one arrow that moves a little every time you pick
an average can only point one way, and you are allowed to like more than one thing. so when a pick lands far from everything you have picked so far, it starts a second average instead of dragging the first off its subject. up to three. each post is scored against the one it is closest to
cos(post, picked) is a post's angle to your average. the feed is sorted by it, closest first
| what you picked | source | cos to the average |
|---|---|---|
| loading | ||
each pick measured against the average of all your picks. picks about one subject all score high. one pick about something else drags the average off and every number drops
what a taste is about is not stored either. the page reads it off today's feed each time: the nearest label from a short list, and the words its nearest headlines share
pick nothing and there is no average. the feed stays newest first
the second hand
what you pass on is averaged too, into a second arrow, and a post's closeness to that one comes off its score
λ is how much comes off. at 0.25, the default, a pass counts a quarter of what a pick does. at 1 a pass cancels a pick. one post is worked at every setting further down, with a ruler
does it workevaluate()
to test a ranker you need a right answer it cannot see. the source a post came from is a good one. the vector does not carry it, and posts from one source tend to share a subject and a voice. so take one source, pick two of its posts, drop the rest back into the pile of …, and see where they end up. lower is better. 1st means the rest of that source came straight to the top
| picks | the ranker | shared words | newest first | shuffled |
|---|---|---|---|---|
| loading | ||||
where the rest of that source lands, the median
one row, worked
the feed only prints the result. this is the arithmetic behind it, for the top post you have not marked yet
- post
- loading
the solid part is the score. the dashed part is what the pass took off
the same row, other λ
| λ | score | rank | what a pass does |
|---|
move this and the row moves on the feed too. λ travels in the link with the rest of your taste
the listingon github
the function as it is on main right now. the notes in the margin belong to the line beside them
where your data lives
- your taste
- in the link and in this browser. the averages themselves, as int8 and base64, plus the ids of the rows you marked. #v=…. nothing is sent anywhere
- the vectors
- made when the page is built, by fastembed's bge-small model, on the build machine. they download as vectors.bin next to posts.json, 270 × 384 int8, and rank.js does the math in your browser
- the cli
- manicule.py rank runs on your machine. give it an opml file and a folder of notes and it writes a markdown page. the only network traffic is fetching the sources
- refresh
- once a day, by a github action. the page is rebuilt from the sources. nothing of yours goes into it