01

The Magic Trick

What knowledge context does — and why it matters in a meeting

Imagine this scenario

You're in a meeting. Someone says "Should we use the new Glean embedding model?" — and instantly, a card slides in on the right side of your screen showing the relevant internal docs about that exact topic.

Nobody searched for anything. Nobody opened a new tab. The app listened to the conversation and surfaced what everyone needed, right when they needed it.

Eridanus — Sprint Planning
Yuki: What's the status of the Glean integration?
Alex: I think we switched to the Chat API last week...
Sam: Should we use the new embedding model or stick with search?
Glean integration
The team migrated from Glean Search API to Chat API in sprint 12. The Chat API provides synthesized answers with citations, replacing raw search results...

That's the Knowledge Context feature

This branch adds ~5,100 lines of new code that make Eridanus understand what people are talking about in real time and proactively surface relevant company knowledge.

Here's the quick version of what the system does:

1
Listen

Every finalized transcript segment gets fed to the knowledge pipeline

2
Understand

An LLM extracts the current topic, open questions, and tensions from a rolling window of conversation

3
Search

When the topic changes significantly, it queries Glean's knowledge base with auto-generated search queries

4
Filter

A surfacing gate scores results on relevance, actionability, and novelty before showing them

5
Surface

Relevant context is broadcast to every participant via WebSocket and appears as collapsible cards

💡
Why this pattern matters

This is a real-world example of a pipeline architecture. The same pattern — listen, understand, act — powers recommendation engines, content moderation, and AI assistants everywhere.

02

Meet the Cast

The six components that make knowledge context work

The crew behind the curtain

Think of this like a newsroom. Each person has a specialty, and they pass information along a chain until a story is ready to publish.

P
Pipeline

The editor-in-chief. Receives transcript segments, decides when to trigger a search, and coordinates everything.

S
StateTracker

The beat reporter. Keeps a rolling window of conversation and uses an LLM to extract the current topic, questions, and tensions.

G
GleanSource / ChatSource

The research desk. Queries the company's knowledge base (Glean) via either search or chat API.

F
Gate

The quality editor. Uses an LLM to score whether results are actually worth interrupting the meeting for.

A
Filter

The fact-checker. Weeds out filler-heavy utterances and detects conversation triggers like questions and disagreements.

C
Client UI (React)

The front page. Renders collapsible knowledge cards with markdown answers and clickable citations.

Where they live in the code

server/internal/knowledge/ All server-side knowledge logic
source.goThe interface every knowledge backend must implement
pipeline.goThe orchestrator: debounce, cooldown, broadcast
state.goConversation state extraction via LLM
gate.goRelevance scoring and surfacing decisions
filter.goUtterance pre-filtering and trigger detection
glean.goGlean Search API adapter
glean_chat.goGlean Chat API adapter (synthesized answers)
aggregate.goFan-out to multiple sources in parallel
client/src/pages/ Client-side rendering
SessionView.tsxKnowledge panel, collapsible cards, manual search
shared/protocol/ Shared types between client and server
messages.tsKnowledgeContextPayload type definition
💡
The Interface Pattern

The Source interface is the linchpin of this design. It defines three methods — Search, Name, Available — that ANY knowledge backend must implement. Today it's Glean. Tomorrow it could be Notion, Confluence, or a local file search. The rest of the pipeline doesn't care which one it's talking to. This "code against an interface, not an implementation" pattern is one of the most powerful ideas in software engineering.

03

The Journey of a Spoken Word

Tracing the path from "Should we use Vitest?" to a knowledge card on every participant's screen

End-to-end data flow

Imagine someone in your meeting says "Should we switch from Jest to Vitest?" — here's the exact path that sentence takes through the system.

🎤
Microphone
H
WebSocket Hub
P
Pipeline
S
StateTracker
G
Glean
💻
All Clients
Click "Next Step" to trace the journey
Step 0 / 8

The WebSocket Hub: the mail room

When the Hub receives a final (non-partial) transcript segment, it does two things simultaneously: broadcasts the text to other participants for their transcript view, and forwards it to the knowledge pipeline.

CODE
// Feed final segments to the knowledge pipeline
if c.Hub.knowledgePipeline != nil {
    c.Hub.knowledgePipeline.OnSegment(c.SessionID, KnowledgeSegment{
        Speaker:  seg.SpeakerName,
        Text:     seg.OriginalText,
        Language: seg.OriginalLanguage,
        IsFinal:  true,
    })
}
PLAIN ENGLISH

If the knowledge system is turned on...

Tell it about this new sentence someone just said, including who said it, what they said, and what language it was in.

Mark it as "final" (not a mid-sentence preview) so the pipeline knows it's safe to analyze.

The Pipeline: traffic control

The Pipeline is the conductor. It first checks if the utterance is worth analyzing, then updates the conversation state, and only triggers a search when the topic has genuinely changed.

CODE
func (p *Pipeline) OnSegment(sessionID string, seg Segment) {
    // Pre-filter: skip short or filler-heavy utterances
    if !isSubstantive(seg.Text) {
        return
    }

    ctx, cancel := context.WithTimeout(context.Background(), llmTimeout)
    defer cancel()

    if err := p.tracker.Update(ctx, sessionID, seg); err != nil {
        return
    }

    if !p.tracker.HasChangedSignificantly(sessionID) {
        return
    }

    p.tracker.AckBroadcast(sessionID)
    p.scheduleSearch(sessionID)
}
PLAIN ENGLISH

When a new sentence arrives...

First, is this sentence worth analyzing? If it's just "yeah, um, ok" — skip it entirely.

Set a 10-second time limit for the LLM call, then ask the StateTracker to update its understanding of the conversation.

Has the topic actually changed from what we've already shown? If not, don't search again — we don't want to spam people with the same results.

Mark this topic as "handled" immediately (so concurrent segments don't trigger duplicate searches), then schedule a knowledge search.

04

The Brain

How conversation state tracking turns messy speech into structured understanding

The rolling window

Think of the StateTracker like a court stenographer with a photographic memory — but they only remember the last 20 sentences. As new sentences come in, old ones scroll off. This "rolling window" is the raw material the LLM uses to understand what's happening.

Rolling Window — Last 20 segments
0 / 4 messages

What the LLM extracts

From that messy conversation, the LLM produces a structured JSON snapshot called the ConversationState:

🎯

topic

"Vitest vs Jest migration" — the specific subject, not a vague label like "testing discussion"

open_questions

"Can Vitest run Jest tests with minimal changes?" — things that are still unresolved

active_tensions

"Speed vs migration risk" — disagreements between participants

🔍

search_queries

"vitest jest migration compatibility" — auto-generated search terms for the knowledge base

Topic change detection: not just string matching

How does the system know when the conversation has moved to a genuinely new topic? It uses Jaccard similarity on content words — filtering out common words like "the", "is", "what" — to compare the new topic against ALL previously shown topics.

CODE
func wordSimilarity(a, b string) float64 {
    setA := contentWordSet(strings.ToLower(a))
    setB := contentWordSet(strings.ToLower(b))
    if len(setA) == 0 && len(setB) == 0 {
        return 1
    }

    intersection := 0
    for w := range setA {
        if setB[w] {
            intersection++
        }
    }

    union := len(setA)
    for w := range setB {
        if !setA[w] { union++ }
    }
    return float64(intersection) / float64(union)
}
PLAIN ENGLISH

Take two topic strings and score how similar they are (0 to 1).

First, extract only the "meaningful" words from each — remove filler words like "the", "is", "and".

If both topics have no meaningful words, treat them as identical.

Count how many meaningful words appear in BOTH topics (the overlap).

Count the total number of unique meaningful words across both topics.

The similarity is: overlap ÷ total. If it's above 0.5 (50%), the topics are "too similar" to trigger a new search.

💡
Stop Words: Signal vs. Noise

Words like "the", "is", "what" appear in almost every sentence. Including them in similarity comparisons would make everything look similar. By filtering them out, the system compares only the meaningful content words — the signal, not the noise. This same technique is used in search engines, document classification, and spam filters. When working with AI tools, you'll see "stop word filtering" pop up everywhere text analysis happens.

05

The Gatekeeper

How the system avoids spamming your meeting with irrelevant cards

The four lines of defense

Surfacing information in a live meeting is high-stakes. Show something irrelevant, and people start ignoring the feature. Show too often, and it becomes noise. The system has four layers of protection, like a building with a bouncer, a metal detector, an ID check, and a guest list.

🚫

Utterance Filter

Rejects short sentences (<8 words) and filler-heavy speech (>60% words like "yeah", "um", "ok")

Cooldown Timer

Minimum 60 seconds between broadcasts. Even if topics shift rapidly, results queue up instead of flooding.

🔄

Topic Deduplication

Compares new topics against ALL previously shown topics using word similarity. Won't re-surface similar content.

🤖

Surfacing Gate

An LLM scores results on relevance, actionability, and novelty. Below 0.6? Rejected. Also penalizes recently shown titles.

The filler filter: first line of defense

CODE
func isSubstantive(text string) bool {
    trimmed := strings.TrimSpace(text)
    if len(trimmed) < minCharCount {
        return false
    }

    words := strings.Fields(strings.ToLower(trimmed))
    if len(words) < minWordCount {
        return false
    }

    fillerCount := 0
    for _, w := range words {
        clean := strings.Trim(w, ".,!?;:'\"")
        if fillerWords[clean] {
            fillerCount++
        }
    }

    return float64(fillerCount)/float64(len(words)) <= maxFillerRatio
}
PLAIN ENGLISH

Is this sentence worth analyzing? Let's check three things.

First, is it long enough? At least 30 characters. "Yeah" doesn't make the cut.

Second, does it have enough words? At least 8. "Sounds good, right?" doesn't either.

Third, strip punctuation from each word and check if it's a filler word (yeah, um, ok, like, basically...).

If more than 60% of the words are filler, this sentence is just noise. Skip it.

Trigger detection: what makes a good search

Beyond topic changes, the system also detects specific signals in speech that suggest a knowledge search would be useful. Think of it like a journalist's instinct for a story — certain phrases are red flags that mean "someone needs information."

question"?" or starts with "what", "how", "why", "should", "do you think"
decision"should we", "let's go with", "which one", "the plan is"
disagreement"i disagree", "that won't work", "on the other hand"
assumption"i assume", "what if", "suppose we"
💬
Debouncing: patience as a feature

Debouncing is crucial here. In a fast conversation, the topic might "change" five times in 30 seconds as different speakers riff on related ideas. Instead of firing five searches, the pipeline resets a timer on each change and only executes when the conversation settles. This is the same technique used in search-as-you-type features.

06

Test Your Understanding

Apply what you've learned to new scenarios

Scenario

You want to add Notion as a second knowledge source alongside Glean. Which file do you need to create, and which existing file do you need to modify?

Where would you start?

Scenario

Users report that knowledge cards keep appearing for the same topic even though the conversation hasn't moved on. The topic is "Q3 budget planning" and it keeps re-appearing every minute.

Where's the most likely bug?

Scenario

You're building a similar knowledge feature for a different app. Your users complain that the system surfaces too many irrelevant results — lots of quantity, low quality. You have both search and chat API access.

What's the most impactful change?

Scenario

Two people in a meeting speak at nearly the same time. Both their finalized segments arrive at the Pipeline within milliseconds of each other. Without any protection, what would happen?

What concurrency problem does AckBroadcast solve?