Brainby arc-labs/docs
Sdk typescriptSearch
TypeScript SDK

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

POST/v1/searchAPI keystable
recall.search(input: SearchInput, options?: TransportRequestOptions): Promise<SearchResult>;
recall.search(input: SearchInput & { stream: true }, options?: TransportRequestOptions): Promise<RecallStream<PipelineEvent, SearchResult>>;

SearchInput

ParameterTypeRequired
querystringrequired

Natural-language query. The understand stage parses it for intent, named entities, and temporal hints ("last week", "yesterday"). Empty strings are rejected with RecallValidationError.

limitnumberoptional

Maximum memories to return. Clamped to [1, 100] server-side. Default 10. Use limit, not topKtopK was the pre-0.5 name and is no longer accepted by the SDK (the server still tolerates it for unmigrated clients).

filters.memoryTypeMemoryTypeoptional

Restrict 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.minConfidencenumberoptional

Minimum confidence threshold in [0, 1]. Default 0.5. Applied in the filter stage. Set to 0 to see every candidate (useful for debugging).

SearchResult

FieldTypePresence
memoriesMemoryWithScore[]always

The ranked result list, in descending order of score. Up to limit items.

totalBeforePolicynumberalways

How many candidates were retrieved across all retrievers before the filter stage applied confidence/freshness/policy cuts. When totalBeforePolicy &gt; memories.length the system filtered some out — useful for debugging "why didn't I see X?".

traceIdstringalways

Server-side trace ID. Pass to support, or correlate with your own logs.

latencyMsnumberalways

Wall-clock pipeline latency in milliseconds.

planstring

Optimizer 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[]:

FieldTypePresence
idMemoryIdalways
memoryTypeMemoryTypealways
One of the seven typed-schema variants.
contentstringalways
Human-readable surface form.
confidencenumberalways
Float in [0, 1] — the memory's stored confidence, after any decay applied by the worker.
scorenumberalways
RRF fusion score for this query. Not directly comparable across queries — use score only to order within one result set.
retrieversstring[]
List of retrievers that contributed (e.g. ['semantic', 'entity_graph']). Useful for debugging "why did this rank?". Omitted when the result was served from cache.
createdAtstringalways
ISO 8601.
updatedAtstringalways
ISO 8601.
tagsstring[]always
Memory tags as stored.

Errors

CodeStatusRetry
UNAUTHORIZED401fatal
Missing or expired API key.
FORBIDDEN403fatal
Key valid but does not permit search on this namespace.
PIPELINE_ERROR422fatal
The read pipeline rejected the query (e.g. empty after sanitization).
UNKNOWN429retriable
Rate limit. Throws RecallRateLimitError with retryAfterMs from Retry-After.
PIPELINE_FAILED500retriable
Mid-stream failure on the streaming endpoint. Auto-retried up to maxRetries.

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?

On this page