Search
Deep reference for recall.search() — input shape, MemoryWithScore output, filters, ranking, streaming overload, and caveats.
The non-streaming form returns a single SearchResult. The streaming overload returns a RecallStream<PipelineEvent, SearchResult> — same final outcome, but with per-stage progress events for UI rendering.
Signature
recall.search(input: SearchInput, options?: TransportRequestOptions): Promise<SearchResult>;
recall.search(input: SearchInput & { stream: true }, options?: TransportRequestOptions): Promise<RecallStream<PipelineEvent, SearchResult>>;SearchInput
querystringrequiredNatural-language query. The understand stage parses it for intent, named entities, and temporal hints ("last week", "yesterday"). Empty strings are rejected with RecallValidationError.
limitnumberoptionalMaximum memories to return. Clamped to [1, 100] server-side. Default 10. Use limit, not topK — topK was the pre-0.5 name and is no longer accepted by the SDK (the server still tolerates it for unmigrated clients).
filters.memoryTypeMemoryTypeoptionalRestrict to a single memory type: fact | preference | event | entity | relation | convention | constraint. Applied in the filter stage after ranking — does not change which retrievers run.
filters.minConfidencenumberoptionalMinimum confidence threshold in [0, 1]. Default 0.5. Applied in the filter stage. Set to 0 to see every candidate (useful for debugging).
SearchResult
memoriesMemoryWithScore[]alwaysThe ranked result list, in descending order of score. Up to limit items.
totalBeforePolicynumberalwaysHow many candidates were retrieved across all retrievers before the filter stage applied confidence/freshness/policy cuts. When totalBeforePolicy > memories.length the system filtered some out — useful for debugging "why didn't I see X?".
traceIdstringalwaysServer-side trace ID. Pass to support, or correlate with your own logs.
latencyMsnumberalwaysWall-clock pipeline latency in milliseconds.
planstringOptimizer plan name (e.g. "EntityCentric", "TemporalRecent"). Set when the understand stage chose a non-default plan; omitted for the default semantic+BM25 fanout.
MemoryWithScore
Each item in memories[]:
idMemoryIdalwaysmemoryTypeMemoryTypealwayscontentstringalwaysconfidencenumberalways[0, 1] — the memory's stored confidence, after any decay applied by the worker.scorenumberalwaysscore only to order within one result set.retrieversstring[]['semantic', 'entity_graph']). Useful for debugging "why did this rank?". Omitted when the result was served from cache.createdAtstringalwaysupdatedAtstringalwaystagsstring[]alwaysErrors
UNAUTHORIZED401fatalFORBIDDEN403fatalPIPELINE_ERROR422fatalUNKNOWN429retriableRecallRateLimitError with retryAfterMs from Retry-After.PIPELINE_FAILED500retriablemaxRetries.Examples
Basic query
const hits = await recall.search({ query: 'where does the user live?', limit: 5 });
for (const m of hits.memories) console.log(m.score.toFixed(3), m.content);
console.log(`returned ${hits.memories.length} of ${hits.totalBeforePolicy} candidates`);Filtered query
const onlyPrefs = await recall.search({
query: 'theme, units, language',
limit: 20,
filters: { memoryType: 'preference', minConfidence: 0.7 },
});Streaming for UI progress
import type { PipelineEvent } from '@arc-labs/recall';
const stream = await recall.search({ query: 'what did we discuss yesterday?', stream: true });
for await (const event of stream) {
switch (event.type) {
case 'stage': console.log(`stage ${event.stage} ${event.status} ${event.latencyMs}ms`); break;
case 'llm_call': console.log(`llm ${event.provider}/${event.model} ${event.tokens} tok`); break;
case 'complete': console.log(`done — plan=${event.outcome.plan ?? 'default'}`); break;
}
}
const result = await stream.finalResult(); // SearchResult
console.log(result.memories);The streaming pipeline emits a stage event for each of the four read stages (understand, retrieve, rank, filter), zero or more llm_call events, and a single complete event at the end. See Streaming for the full event shapes.
Ranking is not pagination
recall.search() returns a ranked result set. There is no cursor, no nextPage, no stable ordering across calls — the same query a minute later may return a different ordering as new memories land or confidence decays. If you want stable browseable lists, use recall.memory.list() instead.
The limit parameter caps the result set. Asking for limit: 100 does not give you "page 2 of search hits" — it gives you the top 100 by score. To explore beyond the top results, narrow your query, lower minConfidence, or change memoryType.
When to use search vs context
recall.search() returns a list of memories with scores. It's the right call when your application wants to display matches, cite sources, or post-process hits with custom logic.
recall.context() returns a Markdown prompt section ready to inject into an LLM system message. It runs the same retrieval but also assembles a per-category breakdown, a token-budgeted prompt, and an optional LLM-generated user summary. Use it when the next step is to feed an LLM, not to render UI.
Caching and idempotency
The server caches identical search payloads for ~30 seconds within a single scope. A repeated query within that window will return faster — and the per-memory retrievers field will be omitted (the cache stores only the ranked output, not the trace).
Search does not accept idempotencyKey. Reads are naturally idempotent.
Common pitfalls
The score field is not comparable across queries. A memory with score: 1.4 for query A is not "more relevant" than one with score: 0.9 for query B. RRF scores are bounded by the number of contributing retrievers and the query's specificity. Use score only to order within one result set.
limit: 1 is faster than limit: 100 but not by as much as you might expect — the read pipeline runs every retriever in parallel and only truncates at the end. Pick a limit that fits your UI; don't try to optimize for latency by shrinking it below 5.
minConfidence: 0 will surface candidates the system would normally hide. Useful for debugging, but unsafe to plumb into LLM prompts — those low-confidence rows are exactly the ones the policy stage was protecting your prompt from.
Was this page helpful?