Skip to content
C.W.K.
Stream
Lesson 05 of 05 · published

Say It, Do Not Infer It: The Category With No Shared Vocabulary

~12 min · ranking, product-design, personalization, limits

Level 0Unsorted
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Where Inference Runs Out

Learned keyword weights are good at one thing: catching a specific story that keeps coming back. Dislike an article, and the tokens naming its subject acquire negative weight, so the follow-ups sink. That works because the follow-ups are literally about the same thing and share its vocabulary.

A whole category is a different object. A cricket report and a football transfer rumour are both sports, and they share almost no exact tokens — different competitions, different names, different verbs. No amount of disliking one moves the other, because there is no lexical bridge between them. The reader experiences this as the system ignoring them: they keep pressing dislike, and the category keeps arriving.

The Honest Answer Is a Text Box

The fix is not a better inference. It is to let the reader say it: a plain list of muted terms, typed in settings, applied deterministically. It feels like a retreat from the interesting machinery and it is strictly better on every axis that matters. It is instant instead of requiring several examples. It is exact instead of probabilistic. It is inspectable, editable, and reversible in one place. And it costs nothing to run.

Saying it out loud is both cheaper and more honest than hoping the scorer guesses. The learned half still earns its place — it catches the recurring story the reader never thought to name — so the two halves are complementary rather than competing: inference for what you did not anticipate, declaration for what you already know.

Verdicts Must Move Cards Immediately

One product rule belongs here because it is the same trust problem. When a reader presses dislike, the card has to leave — everywhere it appears, right then. Early on, dislike bent only the personalized shelf, so pressing it on a topic shelf appeared to do nothing at all. The signal was recorded correctly and the reader had no way to know that.

Pressing a button and then reloading to confirm it worked is not a projection being lazy; it is a bug. If a verdict is real, the projection that shows it must be recomputed before the response returns.

Some Shelves Are Never Filtered

The last piece is a boundary. Muting applies to discovery surfaces — topic shelves and the personalized feed — and never to the shelves that hold what the reader deliberately kept. A saved article does not vanish because a muted term appears in it; that would make the save button untrustworthy, which is a far worse outcome than a stray sports headline.

Inference and declaration solve different problems, and neither is a fallback for the other. If your only control is learned, the reader cannot express a category; if it is only declared, they must anticipate everything. The mistake is treating the text box as an admission of failure rather than as the half that handles what people already know.

Code

Declared terms and learned weights as complementary halves, with a boundary·python
# The deterministic half of muting: the reader's own list, typed in
# Settings. Learned weights catch a story that keeps coming back; a
# whole CATEGORY shares little exact vocabulary between a cricket
# report and a transfer rumour, so inference alone will always leak.
MUTED_TERMS_KEY = "muted_terms"


def apply_mutes(con, rows, profile=None) -> tuple[list[dict], int]:
    """Drop what the reader has told us they don't want.

    Returns (kept, muted_count) -- the count is returned, never
    swallowed: a shelf that quietly shrinks is a black box.
    """
    if not rows:
        return rows, 0

    declared = set(get_setting(con, MUTED_TERMS_KEY, []))   # said out loud
    profile = mute_profile(con) if profile is None else profile
    common = _common_tokens(rows)                           # DF guard

    kept = []
    for row in rows:
        toks = tokens(row)
        if declared & toks:
            continue                       # exact, instant, inspectable
        if mute_score(row, profile, skip=common) <= MUTE_THRESHOLD:
            continue                       # learned, for what wasn't named
        kept.append(row)
    return kept, len(rows) - len(kept)


# The boundary: discovery surfaces are filtered, kept ones never are.
# A saved article vanishing because a muted term appears in it would
# make the save button untrustworthy -- a far worse outcome than a
# stray headline.
MUTABLE_SHELF_KINDS = {"topic", "foryou"}      # not saved, not queued

External links

Exercise

Find a personalization feature you use and try to express a whole category you never want to see. Note whether the product gives you a way to say it, or only a way to react to individual items. Then count how many individual reactions it would take to teach it the category by example — and whether the items even share enough surface for that to converge.
Hint
Categories that resist inference are the ones defined by a concept rather than by words: sports, celebrity news, anything about a particular emotion or tone. Categories that yield easily are proper nouns. If a product only offers per-item feedback, it is implicitly assuming every category you dislike is a proper noun.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.