π Recall Memories
recall searches all memories and returns the best matches. The two engines search in different places.
Batched engine: local hybrid searchβ
Search runs fully locally against the index (flushed and pending); no network round trips:
const matches = await memory.recall('how do I escalate for ACME?', {
tags: ['customer'], // optional: only memories carrying at least one of these tags
limit: 5, // default 5
})
console.log(matches[0])
// {
// id: '80cc2055-β¦',
// content: 'Customer ACME is on the enterprise plan; β¦',
// score: 0.62, // final ranking score
// semanticScore: 0.71, // cosine similarity (0..1, when embeddings active)
// keywordScore: 0.41, // keyword/tag overlap
// tags: ['customer', 'escalation'],
// cid: 'bafβ¦',
// gatewayUrl: 'https://gatewayβ¦/ipfs/bafβ¦'
// }
How scoring worksβ
With the local embedder (the default):
score = 0.7 Γ cosine(query, memory) + 0.3 Γ min(1, keywordScore)
- Semantic component - query and memories are embedded locally with
all-MiniLM-L6-v2(in-process; the ~25 MB model downloads once, and no content leaves the machine). This is what lets "which hosting platform is used for releases?" find "deploys to Vercel on the main branch" with zero shared keywords. Vectors carry their model id (embeddingModel); recall only compares same-model vectors, so switching models safely falls back to keyword until backfilled. - Keyword component - matching content words dampened by memory length (
hits / βwords), plus+0.5for each tag that appears in the query. - Ties break by recency.
Pass embedder: null (or MEMORY_EMBEDDER=keyword) for word-match only - zero deps, fully offline. If the embedding model cannot load (e.g. offline on first use), recall degrades gracefully to pure keyword scoring - status() reports which mode is active.
Memwal engine: relayer vector searchβ
Search runs in the relayer over the encrypted blobs; results are enriched with your local tags/CIDs:
const matches = await memory.recall('how do I escalate for ACME?', {
tags: ['customer'], // applied locally after the relayer returns hits
limit: 5, // default 5
maxDistance: 1.2, // optional: drop matches with distance >= this value
})
console.log(matches[0])
// {
// blobId: 'β¦', content: 'β¦', distance: 0.38, score: 0.62,
// id, tags, agent, createdAt, cid, pinned, gatewayUrl, walrusUrl,
// indexed: true // false = relayer knows it, this machine has no local record yet
// }
score is 1 β distance clamped to 0..1. There is no local embedder to configure - memwal needs none (remote).
Other read operationsβ
await memory.list({ limit: 20 }) // newest first (memwal also accepts { tags })
await memory.get(idOrCid) // full record by id or CID (memwal: also Walrus blob id)
await memory.status() // engine, namespace, counts, index state
Engine extras:
// batched
await storage.getBlobIds(cid) // Walrus-native blob IDs (lh-ipfs-walrus only, else [])
// memwal
await memory.verify(ref) // integrity: gateway byte-compare if pinned, else local re-hash
await memory.blobIds(ref) // { memwalBlobId, recordBlobIds }
await memory.analyze(text, { occurredAt }) // extract discrete facts into separate memories
await memory.restore({ maxBlobs }) // ask relayer to rebuild missing vector-index entries
await memory.repinPending() // retry failed IPFS pins (see pendingPins in status)
Every flushed batch blob / pinned memwal record is also readable by anyone directly from the gateway:
curl https://gateway-walrus.lighthouse.storage/ipfs/<CID>
curl https://gateway.lighthouse.storage/ipfs/<CID>