recall

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package recall is a tiny full-text index over saved sessions. It powers the /recall slash command — search across every conversation you've had with yottacode, ranked by FTS5 relevance.

The index lives at ~/.yottacode/index.sqlite and is rebuilt incrementally whenever a session is saved. At startup the TUI re-indexes every session in a background goroutine so historical sessions become searchable.

Index

Constants

View Source
const (
	ScopeProject = "project"
	ScopeUser    = "user"
	ScopeAll     = "all"
)

Scope values for SearchSemantic. "project" restricts recall to sessions in the caller's project (see projectScopeClause); "user"/"all" (and anything else) search the whole store. The store is already per-user under ~/.yottacode, so "user" and "all" are equivalent today — both are accepted so the config vocabulary in the plan stays intact. There is no "off" scope: auto-recall is switched off with `auto = false`.

Variables

This section is empty.

Functions

func Backfill

func Backfill(idx *Index) error

Backfill walks every saved session via session.List + session.Load and re-indexes them. Cheap when the corpus is small (a few dozen sessions); expected to run as a background goroutine at TUI startup.

Types

type Embedder added in v0.4.0

type Embedder interface {
	Embed(ctx context.Context, text string) ([]float32, error)
}

Embedder is the narrow slice of memory.EmbedClient that vector backfill needs. Kept as an interface here so the recall package doesn't import memory (which would risk an import cycle and pull an HTTP client into a storage package). *memory.EmbedClient satisfies it.

type Hit

type Hit struct {
	SessionID   string
	SessionName string
	Model       string
	Created     time.Time
	Role        adapter.Role
	MsgIndex    int
	Snippet     string
}

Hit is one search result: the message that matched plus the surrounding session metadata. Snippet contains FTS5-highlighted match context with `[…]` brackets around the matched terms.

type Index

type Index struct {
	// contains filtered or unexported fields
}

Index is the writable handle on the FTS5 database. Safe to share across goroutines; writes through one Index are serialized, and transient SQLite writer contention from other handles/processes is retried.

func MustOpenForTest

func MustOpenForTest(t interface {
	TempDir() string
	Fatalf(string, ...any)
}) *Index

MustOpenForTest opens a fresh in-temp-dir index for use from another package's tests. Panics on failure (which would mean the test fixture itself is broken). Lives in the production file so it's exported, but is only intended to be called from *_test.go.

func Open

func Open() (*Index, error)

Open returns the index living at ~/.yottacode/index.sqlite, creating the directory and schema as needed.

func (*Index) BackfillVectors added in v0.4.0

func (idx *Index) BackfillVectors(ctx context.Context, e Embedder, model string) error

BackfillVectors embeds every message across all sessions that is missing or stale for the given model. Intended to run in a background goroutine at startup.

func (*Index) BackfillVectorsForSession added in v0.4.0

func (idx *Index) BackfillVectorsForSession(ctx context.Context, e Embedder, model, sessionID string) error

BackfillVectorsForSession embeds only the given session's missing/stale messages — the cheap incremental path run after each turn, so a conversation becomes recallable in later sessions without waiting for the next startup backfill. Typically embeds just the turn's new user/assistant messages.

func (*Index) Close

func (idx *Index) Close() error

Close releases the underlying database handle. It's fine to ignore the error during shutdown.

func (*Index) IndexSession

func (idx *Index) IndexSession(s *session.Session) error

IndexSession upserts every user/assistant message body for the given session. Replaces any prior FTS rows for that session id, so re-indexing after a turn is correct (and cheap — sessions are at most a few KB).

func (*Index) PutVector added in v0.4.0

func (idx *Index) PutVector(sessionID string, msgIndex int, model, content string, vec []float32) error

PutVector upserts one message embedding, recording the content hash so a later re-index can detect if the underlying message text changed. Serialized through the write mutex and retried on SQLite writer contention, matching IndexSession.

func (*Index) Search

func (idx *Index) Search(query string, limit int) ([]Hit, error)

Search runs an FTS5 MATCH query and returns up to `limit` hits sorted by rank (most relevant first). limit defaults to 10 when ≤ 0.

The first attempt uses the raw query so power users can still write FTS5-native expressions ("auth OR jwt", "\"exact phrase\"", etc.). On a syntax failure we retry with a sanitized version so naive inputs like "nothing-matches-this" — which FTS5 reads as a NOT operator — still produce a clean empty result instead of a SQL error.

func (*Index) SearchSemantic added in v0.4.0

func (idx *Index) SearchSemantic(queryVec []float32, opts SemanticSearchOpts) ([]ScoredHit, error)

SearchSemantic ranks stored message embeddings by cosine similarity to queryVec and returns the top hits at or above opts.MinScore. Only vectors produced by opts.Model are considered — a vector from another embedding model lives in a different space and its cosine would be noise. Project scope restricts to sessions sharing opts.Cwd; the live session is excluded via opts.ExcludeSession.

func (*Index) UnvectoredMessages added in v0.4.0

func (idx *Index) UnvectoredMessages(model string) ([]MsgRef, error)

UnvectoredMessages returns indexed messages across all sessions that lack a current embedding — see unvectoredMessages for the staleness rules.

type MsgRef added in v0.4.0

type MsgRef struct {
	SessionID string
	MsgIndex  int
	Content   string
}

MsgRef identifies one indexed message that still needs an embedding.

type ScoredHit added in v0.4.0

type ScoredHit struct {
	Hit
	Score float64
}

ScoredHit is a semantic search result: a Hit plus its cosine score against the query vector.

type SemanticSearchOpts added in v0.4.0

type SemanticSearchOpts struct {
	Model          string   // embed model; only vectors from this model are ranked
	Scope          string   // ScopeProject | ScopeUser | ScopeAll
	Cwd            string   // used when Scope == ScopeProject
	ProjectRoots   []string // roots that count as "this project" for ScopeProject; sessions at or below any of them match. Empty → exact Cwd only
	ExcludeRoots   []string // sensitive project roots; sessions at or below any of them never match, whatever the Scope
	ExcludeSession string   // session id to omit (usually the live session)
	Limit          int      // max hits; defaults to 10 when <= 0
	MinScore       float64  // cosine floor; hits below this are dropped
}

SemanticSearchOpts parameterizes SearchSemantic.

Jump to

Keyboard shortcuts

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