Skip to content

Retrieval

Filipe Soares edited this page Oct 8, 2026 · 3 revisions

Retrieval

search and recall are keyword search: SQLite FTS5 ranked by BM25. No embedding model, no network call, no GPU. Retrieval only narrows the candidates — whether a result answers the question is the agent's call.

Writing queries

Spend terms freely. Each word is matched on its own, and a memory that matches more of them ranks higher. Piling synonyms, the identifier and the plain-language phrasing into one query costs one call and finds more:

search("stripe webhook duplicate retry idempotency event id charge.succeeded")

Common words score near zero, so a whole sentence is fine too. Words are stemmed (porter), so retry also finds retries.

Pasting a uid returns that memory first, then the memories that mention it.

What makes a memory findable

Each column carries a different weight:

column weight why
title 1.5 one line chosen to name the memory
content 1.0 the body
tags 0.8 the synonyms the body does not use
domain, also 0.3 a scope name is findable, but filing under acme/cache should not outrank the memory that discusses the cache

Tip

Tags matter more than they look. A memory with no tags answers only a query that quotes its own wording. A writer is told how many tags it indexed, and the dashboard's Health view lists the untagged ones.

What a result carries

field means
match_source fts, or uid for the memory a pasted id names
fts_rank the BM25 score; lower is better
est_tokens what a get_memory(uid) of the full record would cost
succeeded_by a newer memory supersedes this one — read that instead
collapsed near-identical results folded into this one
confidence a contradicted memory sorts behind everything that still holds

Listings truncate content at 400 characters and point at get_memory(uid) for the rest. A result that would run past 40,000 characters comes in pages: pass its next_offset as offset for the next one. See Tools.

When search comes back thin

try because
more synonyms in the same query each extra term widens the net at no cost
list_by_domain(domain) newest first, everything under a path
must_read(domain, type=…) headers of what is open in a scope
timeline(uid) what was written around a memory you did find

pulse, must_read and the list_* calls sort by creation date, never by similarity, so the newest checkpoint is always the one returned.

Clone this wiki locally