recall

package
v0.30.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package recall ranks notes by relevance to a free-text task context so a session can surface the lessons/gotchas a past session recorded BEFORE it repeats the mistake — the "learn from previous sessions" loop.

Why this exists: nt_search is substring-AND (every term must appear verbatim), which misses paraphrases — an agent about to add "parallel request handling" won't type the exact words of a note titled "goroutine deadlock". recall trades precision for recall in the one place that needs it: it tokenizes the context, drops stopwords, applies light stemming, and expands each token across a small map of dev-concept synonyms, then scores notes by concept overlap. Notes tagged `lesson` are boosted so durable gotchas rise above ordinary reference notes.

It is deliberately dependency-free (no embeddings/index) — a good-enough recall lift that stays instant on a plain-file store. When that ceases to be enough (very large corpora, subtle paraphrase), a vector index is the natural next step.

Index

Constants

View Source
const LessonTag = "lesson"

LessonTag marks a note as a durable lesson/gotcha — the class recall surfaces first. Capture with `nt note --lesson` (or tag an existing note `lesson`).

Variables

This section is empty.

Functions

func ExplainProject added in v0.29.0

func ExplainProject(notes []*note.Note, context string, limit int, project string) ([]Result, *Trace)

ExplainProject is RankProject with a full scoring trace attached: which query terms hit which note at what strength, plus which candidates the precision floor, tail trim, or --limit dropped and why. Ranking itself is identical to RankProject/Rank — see TestExplainMatchesRank.

func LoadUserSynonyms added in v0.24.0

func LoadUserSynonyms(dir string) error

LoadUserSynonyms reads $dir/synonyms.txt — one synonym group per line, words separated by commas/whitespace, '#' starts a comment, blank lines ignored — and merges it over the built-in table. A line sharing a word with a built-in group extends that group (e.g. add a house term to "concurrency" without knowing the rest of the list); an all-new line mints its own group. Cheap enough to call before every ranking — callers do, so an edited synonyms.txt takes effect on the next recall with no restart. A missing file is not an error — nothing to load; treat any other error as best-effort too (a malformed synonyms file shouldn't break recall).

Types

type NoteTrace added in v0.29.0

type NoteTrace struct {
	ID, Title                     string
	Hits                          []TermHit
	Raw, Final                    float64 // pre-boost / post-boost score
	Lesson, ProjectMatch, Expired bool    // which boosts fired
	Excluded                      string  // "" | "no-match" | "precision-floor" | "tail-trim" | "limit"
	// StrongTerms is the note's strong-bag (title/tags/description) vocabulary
	// after stemming — only populated for an ExplainNote target. It's the
	// actionable half of "why is my note missing": the words it WOULD need to
	// share with the query.
	StrongTerms []string
}

NoteTrace is one note's full scoring decomposition: how each query term scored against it, what it summed to before/after boosts, and — for a dropped candidate — why it isn't in the printed results.

type Result

type Result struct {
	Note         *note.Note
	Score        int
	Lesson       bool
	ProjectMatch bool // note belongs to the caller's project (soft ranking boost applied)
	Expired      bool // note.Note.Expired() as of ranking time — valid_until has passed
	// Faded/DecayFactor: relevance decay as of ranking time (memory-dynamics
	// spec §3). Faded = aged past one half-life un-reconfirmed; DecayFactor is
	// the multiplier applied to the ranking score (1.0 = no decay). Like the
	// expired flag: a signal to doubt, never a filter.
	Faded       bool
	DecayFactor float64
	// Confidence is the PRE-BOOST score over the best score this query could
	// possibly award (see fMax below) — comparable across queries and store
	// sizes, unlike Score, which is IDF-scaled and therefore query-dependent.
	// Boosts (lesson/project/expired) are deliberately excluded: they express
	// preference, not evidence, and folding them in would let a thinly-matched
	// lesson print as confident — the exact promotion pathology the precision
	// floor already fights. Matched/QueryTerms are the raw coverage fraction
	// (m/n query words that hit at all), shown alongside the tier because a
	// tier alone hides *how much* of the query a note actually covers.
	Confidence float64
	Matched    int
	QueryTerms int
}

Result is one ranked note. Lesson notes sort first at equal relevance.

func Rank

func Rank(notes []*note.Note, context string, limit int) []Result

Rank scores active notes against the task context and returns the most relevant, lesson notes boosted. A note scores 3 per concept matched in its title/tags/ description (high-signal fields) or 1 in its body; lesson notes get a flat boost so a relevant gotcha outranks a merely-adjacent reference note. Notes with no overlap are dropped. limit<=0 means no cap.

func RankProject added in v0.22.0

func RankProject(notes []*note.Note, context string, limit int, project string) []Result

RankProject is Rank with a soft same-project preference: notes tagged (or foldered) as belonging to `project` — typically the caller's NT_WORKSTREAM — rank above equally-relevant notes from other projects. Empty project means no preference (identical to Rank).

func (Result) Tier added in v0.29.0

func (r Result) Tier() string

Tier buckets Confidence into a word an agent never has to interpret as a float: "strong" | "medium" | "weak". Kept as a method (not a package func) so CLI and MCP read it off the Result and can't drift into re-deriving it.

type TermHit added in v0.29.0

type TermHit struct {
	Term    string  // stemmed query word
	Concept string  // synonym-group id ("g0"…), or the word itself if ungrouped
	Where   string  // "strong-exact" | "strong-syn" | "weak-exact" | "weak-syn" | ""
	Base    float64 // 4, 2, 2, 1, or 0 — matches Where
	IDF     float64
}

TermHit is one query term's outcome against one note. Where is empty when the term didn't match anything in the note at all.

type Trace added in v0.29.0

type Trace struct {
	QueryTerms  []string // stemmed, sorted — the scorer's actual fixed iteration order
	NumNotes    int
	FloorActive bool
	TargetID    string // set by ExplainNote; empty for ExplainProject
	Notes       []NoteTrace
}

Trace is the scoring trace for one recall call: ExplainProject fills Notes with the printed results plus up to explainExcludedCap dropped candidates; ExplainNote fills it with exactly one NoteTrace for the requested note, whether or not that note ever became a candidate.

func ExplainNote added in v0.29.0

func ExplainNote(notes []*note.Note, context, project, id string) (*Trace, error)

ExplainNote traces ONE note against the query, whether or not it ever became a candidate — a note with zero term overlap never enters RankProject's output at all, so no list-level --explain can surface it. limit is unbounded: the target's own fate (kept, or dropped by the floor/ trim/limit) must not depend on how many rows the caller asked to see.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL