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

A Hint From the Talk, Not the Roster

~14 min · keyterms, speech-to-text, tf-idf, korean-nlp

Level 0Muted
0 XP0/61 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete
"The hint is the word the transcript should hold." — the transcriber hint module's own docstring

Croquettes, Heard as Buffett

On 2026-10-17, in a loud tapas bar in Málaga, Dad told Pippa "크로켓은 여기가 최고다": the croquettes here are the best. The turn came back as "버핏은 여기가 최고다", Buffett is the best here. It was a short utterance in voice mode on the phone, so Scribe v2 Realtime heard it over the phone's own socket and no batch pass ever touched it. Nothing in that bar sounded like an investor. The word came from the hint. Every voice door sent Scribe the name of every soul the current one can see (this track's second lesson), and one of those souls is Buffett, 버핏 in a Korean session.

The kept recording made it measurable. Replayed through Scribe's batch model with that list, it read 버핏 six times out of six. With no hint at all it read 뽀켓, 버켓 or 코켓. With the soul being spoken to plus this conversation's own rare words, it read 크로켓 six times out of six. A keyterm is a bias, and a bias toward a name nobody said is a bias toward a mistake.

Two Parts, Built From the Talk

Since 2026-10-18 one module builds the hint for every door. The batch /api/stt (dictation, the Watch, voice mode's whole pass), the realtime socket's grant and the speaker check's labeled transcript all ask the same function, so the phone, the Watch and the web hear with the same words.

  • Souls. The soul being spoken to always rides (피파 is also how FIFA is said). Any other soul rides only when the last turns have named it. A Korean session still gets the Korean names.
  • Topic words. Words from the last 30 turns that are rare in Dad's history as a whole. Each turn's words are weighted by age, from 1.0 for the newest down to 0.5, and Dad's own turns count double. A word is rare when at most 0.3 percent of a background sample holds it, with a floor of three turns. The background is about 4,000 turns spread across every conversation, rebuilt every six hours while the old copy keeps answering: 크로켓 sat in 11 of 4,322 of them, 호텔 in 111. Rare words are scored the way tf–idf scores them (the weight, times one plus the log of how rare), and the top 20 go. On the Málaga recording, 20 read 크로켓 six times out of six and 30 read it five out of six.

Everyday words stay out on purpose. Offered as candidates, they won: one replay read "호텔은 여기가 최고다". A hint is not a summary of the conversation. It is the short list of words the transcriber would otherwise never guess.

Korean Needs a Noun Filter

Korean words arrive wearing their particles. 크로켓은, 크로켓이랑 and 크로켓 are one word, so the particle comes off, and a stem keeps at least two syllables. A copula comes off the same way, so 입구야 becomes 입구. A word counts only if it looks like a noun: it carries a particle that only nouns take, or it stands alone at least twice, and it does not end the way a predicate ends (맛있게, 있었어, 걷다가). A stub too short to cut that still ends in a particle (역까지, 툴을) no longer counts as standing alone. An English session takes Latin words of three letters or more; any other language gets no topic words at all. Before any of this, the stored turn's non-speech is stripped: attachment lines, links, audio tags, a card's JSON keys, inline code.

Scribe's Rules, and What the Hint Cannot Do

Every term keeps Scribe's own rules (at most 49 characters and five words, none of <>{}[]\), and the whole list stays at 100 or under: past 100 terms Scribe bills a 20-second minimum per request. A private conversation gets the soul part only, so its words never ride any request but its own. PIPPA_STT_CONTEXT=0 sends the souls alone. A sweep over 35 kept recordings changed only the Málaga phrase in substance.

One limit stays, and the module says so. The realtime model misread that phrase whatever it was told (버핏, 코프스는), and its previous_text field did not help. The hint fixes the batch pass: long utterances, dictation, and every utterance once Dad switches on noisy-place mode (Track 8), which sends each one through the batch pass whole. A short utterance in a quiet room never needed the help. One in a loud bar needs the mode.

Code

A minimal sketch: the soul spoken to, souls the talk just named, and the talk's rare words·python
import math
import re
from collections import Counter

RECENT_TURNS = 30          # the last turns read, newest first
TOPIC_TERMS_MAX = 20       # topic words sent
RARE_SHARE = 0.003         # rare: held by at most this share of background turns
RARE_FLOOR = 3.0           # ...or by this many, whichever is larger
JOSA = sorted("에서는 이랑 에서 까지 부터 처럼 으로 이야 이다 은 는 이 가 을 를 에 도 만 의 와 과 랑 로 야 다".split(),
              key=len, reverse=True)
HANGUL = re.compile(r"[가-힣]{2,}")
# A predicate's ending, never a word Dad names (왔어, 유명하대, 맛있게).
PREDICATE = re.compile(r"(어|대|게|요|해|하|다|니까|는데)$")


def stem(word: str) -> str:
    """크로켓은 -> 크로켓, 입구야 -> 입구; a stem keeps two syllables."""
    for josa in JOSA:
        if word.endswith(josa) and len(word) - len(josa) >= 2:
            return word[: -len(josa)]
    return word


def noun_like(word: str) -> str | None:
    """A noun with its particle off, or None for a predicate or a stub (바에).
    The engine also asks for noun evidence (a particle only nouns take, or the
    bare word standing alone twice); this sketch leaves that rule out."""
    base = stem(word)
    if base == word and any(word.endswith(j) for j in JOSA):
        return None                      # too short to cut, still ends in a particle
    if PREDICATE.search(base):
        return None                      # 왔어, 유명하대, 걷다: a verb, not a topic
    return base


def words(text: str) -> set[str]:
    return {w for w in map(noun_like, HANGUL.findall(text)) if w}


def topic_terms(recent: list[tuple[str, str]], background: list[str]) -> list[str]:
    """recent: (role, text), newest first. Rare in the history, used here now."""
    seen = Counter(w for turn in background for w in words(turn))
    n = len(background)
    rare_line = max(RARE_FLOOR, n * RARE_SHARE)
    score: Counter = Counter()
    turns = recent[:RECENT_TURNS]
    for age, (role, text) in enumerate(turns):
        weight = 1.0 - 0.5 * age / max(1, len(turns) - 1)   # 1.0 newest -> 0.5 oldest
        if role == "dad":
            weight *= 2                                       # his own words count double
        for w in words(text):
            if seen[w] <= rare_line:
                score[w] += weight * (1 + math.log((n + 1) / (seen[w] + 1)))
    return [w for w, _ in score.most_common(TOPIC_TERMS_MAX)]


def keyterms(spoken_to: str, souls: dict[str, str], recent, background) -> list[str]:
    """The soul spoken to always; another soul only when the talk just named it."""
    named = [k for k in souls.values() if any(k in text for _, text in recent)]
    terms = [souls[spoken_to]] + [k for k in named if k != souls[spoken_to]]
    return terms + [t for t in topic_terms(recent, background) if t not in terms]


# A history of 4,000 turns: hotels and dinners everywhere, croquettes almost never.
background = (["호텔 체크인 끝났어"] * 111 + ["크로켓 맛있었어"] * 11
              + ["오늘 저녁 메뉴 뭐야"] * 600 + ["여기 근처 최고 맛집 알려줘"] * 400
              + ["내일 일정 알려줘"] * 2878)
souls = {"pippa": "피파", "buffett": "버핏", "feynman": "파인만"}
recent = [("dad", "크로켓은 여기가 최고다"),
          ("pippa", "그 집 크로켓이랑 하몬 둘 다 유명하대"),
          ("dad", "호텔 근처 타파스 바에 왔어")]

print("every soul's name :", list(souls.values()))
print("the new hint      :", keyterms("pippa", souls, recent, background))


# Output:
# every soul's name : ['피파', '버핏', '파인만']
# the new hint      : ['피파', '크로켓', '타파스', '하몬']
#   (호텔 is in 111 of 4,000 turns: too common to be this talk's word)

External links

Exercise

Run the sketch. Then add a turn in which Dad mentions 파인만 and check that Feynman's name now rides the hint, and that Buffett's still does not. Next, raise RARE_SHARE to 0.2 and see which everyday words slip in. Finally, write the English half: Latin words of three letters or more, lower-cased, under the same rarity rule, and explain why it needs no stop list.
Hint
With 파인만 in a recent turn the soul part grows by one and nothing else changes. At 0.2 the rare line jumps to 800 background turns, so 호텔 (111) and the 400-turn 여기 근처 최고 words pass, and the hint starts offering the transcriber words it already knew. The English half needs no stop list for the same reason: 'the' and 'and' sit in nearly every background turn, so the rarity rule throws them out before ranking ever sees them.

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 in — Please sign in to comment.

No comments yet — be the first.