Client initialization
timeoutMs is per attempt, so with maxRetries: 2 a call can take up to three
timeouts plus its backoffs. reason and askAboutUser override it to 90
seconds of their own, because a thorough synthesis legitimately runs that long.
The client does not read an environment variable automatically, so pass apiKey
explicitly. Requests carry no state between calls, so a single client can be shared
across your whole application.
fetch is injectable, which is useful for tests or a custom transport:
Methods
Response objects keep the API’s field names, so they are
snake_case:
memory_id, relevance_score, space_id, job_ids.
record, recordBatch and retrieve take either spaceId or
route: "auto", never both and never neither — the types make the wrong pairing
a compile error rather than a 422 route_conflict found at runtime.
fallbackSpaceId and maxTargets apply only to the routed form.
Two places where the published types are currently narrower than the API, both
tracked in ANO-443:
CatalogModelis missing three fields thatGET /v1/modelsalways returns:default(whether this is the model a caller gets by omitting the field),provider(which host serves it) andsupports_memory_operations(falsefor a model the chat proxy serves butreasonand the memory models cannot). They are present in the response, so reading them works — the type just will not admit they exist. Cast, or read the catalog fields.createMemoryModelis typedPromise<MemoryModel>, and the API answers202with a job —{ model_id, job_id, status }, nocontent. PollgetJoband thengetMemoryModel.refreshMemoryModelis typed correctly as a job.
Parameters the REST API has and this client does not
Also note
topK is an alias for limit, not a candidate bound: it is sent
in place of limit and wins when both are given.
record
background: true to queue the write instead of waiting on extraction. The call
then returns job_id with status: "processing" and memory_id: null, so poll it with
getJob.
userId, agentId and sessionId scope the memory inside the space, so one space can
serve many end users without their memories mixing: only a retrieve carrying the same
scope sees it. See Scoping.
recordBatch
Up to 100 memories in one call, always queued.retrieve
source_ids is always present, so it is safe to iterate without a guard. Because a
note is derived, its own metadata is empty and its document_id is null: those
belong to the facts behind it. Pass preferObservations: false to get those facts back
alongside their note, each carrying the metadata you wrote it with.
relevance_score is a composite of four factors, not a normalized probability, so it can
exceed 1.0. It is null for memories returned outside a ranked recall.mode: "fast" skips the neural rerank pass for lower latency, at some cost to relevance
quality. See Latency modes.
Retrieval collapses a consolidated memory and the raw facts behind it into one result by
default. Pass preferObservations: false to see both layers. Other filters:
memoryType, tags, tagsMatch, tagGroups, minScore, asOf.
tagGroups is a boolean tag filter, for what a flat list under one tagsMatch
cannot say: an OR of alternatives, or an exclusion. It is typed as a TagGroup
union, so a malformed filter is a compile error rather than a 422.
asOf restricts the search to memories recorded at or before an ISO 8601 instant,
so you get what the space knew then. queryTimestamp is not a filter. It moves the
“now” that recency scoring and relative dates in the query are measured against, and
never removes a result. See
Searching over time.
retrieveReceipt, getReceipt and explain
retrieve returns a plain array, which has nowhere to carry the id of the
receipt for that search. retrieveReceipt is the same search and returns both.
receiptDetail: "full" also asks the search to account for its own cuts, so the
receipt can explain memories that were ranked and dropped before limit or a
relevance floor ever applied. It can cost latency on the first call for a given
query, so leave it "basic" for production traffic.
receipt_id is null only when the receipt could not be stored. A receipt is a
debugging aid and never load-bearing, so the search itself still succeeded and
memories is complete either way.
explain answers the question the receipt cannot: why one specific memory is
not in your results.
not_retrieved is the one to act on: nothing matched the memory at all, so a
bigger limit or a lower floor will not bring it back, and the query wording or
the scope you searched is what to change. arms shows which kind of matching
found it, and how well: found by keyword but not semantic usually means your
query shares words with the memory but not meaning.
Every search builds a receipt whether or not you asked for one, so getReceipt
and explain also work on a request id from your own logs. See
Context receipts.
reason
usage is available alongside insights.
getUserProfile and askAboutUser
Everything a space has learned about one end user, and a question answered from that user’s memories only. Both needuserId to be the same value your writes
are scoped with.
userId nobody has recorded under is not an error: it comes back with
memory_count: 0 and an empty memories, because a user is a scope tag created
by the first write naming it rather than a resource you register. An unknown
space is still a 404.
memory_count can also go down between two reads, since consolidation folds
several raw facts into one note and the default view counts the note. Read it as
how many distinct things are currently known about this user, not as an
ingestion counter. See User profiles.
updateMemory
Correct a memory, or retire it without losing it.state: "invalidated" drops the memory out of retrieve, consolidation and
reasoning while keeping it for audit; "active" puts it back. That is a
supersession, not a delete, so prefer it over deleteMemory, which is permanent.
Editable fields: text, context, occurredStart, occurredEnd,
memoryType, entities, state, reason. At least one is required. A call
with nothing to change fails locally, before any request. reason is kept on
the memory’s history so an audit shows why it changed, not merely that it did.
Memories the system synthesised from your raw facts cannot be edited. They are
derived, so the API rejects the attempt rather than letting a synthesis drift
away from the evidence under it.
getMemoryHistory
{ memory_id, history }, where each entry carries the previous content
and when it changed.
Entries come from two places: Anona’s own consolidation rewriting a memory, and
your edits with
updateMemory. Editing a memory adds one entry carrying its
previous_content, changed_at and the reason you passed, so an edit can be
audited straight away. An empty history means the memory has never changed.uploadFiles
Uploads files into a space so retrieval can draw on their content. Ingestion is asynchronous and returnsjob_ids, so poll each with getJob.
data accepts a Blob, File, ArrayBuffer, or Uint8Array. A Node Buffer works
unchanged. There is no filesystem-path overload, which is what keeps the package usable
on edge runtimes.
All three limits are enforced before anything is sent, so an oversized upload fails
instantly rather than after transferring the body:
Supported formats are PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, HTML, TXT, Markdown,
CSV, JPEG/PNG images, MP3/WAV audio and MP4/MOV/WEBM/MKV video. Images, audio
and video are read into text. See Documents.
Vercel AI SDK
Wrap any model to give it memory. Relevant memories are recalled before the call and the turn is recorded after it.ai v5 or newer. Recording is fire-and-forget by default so memory never adds
latency to a response; pass await: true to block until the write lands, or
record: false to recall without writing. Streaming is supported, and the turn is recorded
when the stream completes, not at the first token.
Memory failures never break the model call. If recall or recording fails, the request
proceeds without it.
OpenAI Agents SDK
Exposes memory as two tools the model can call:remember and recall.
Error handling
429 is retried automatically on every call, with the wait the server named.
A 5xx and a dropped connection are retried only where a replay is safe,
because a 5xx may mean the request landed. These ten calls opt out:
record, recordBatch, reason, askAboutUser, uploadFiles,
createSpace, createWebhook, createRule, createMemoryModel and
refreshMemoryModel. Everything else — retrieve, getContext,
retrieveReceipt and every GET, PUT and DELETE — is retried. 4xx is never retried — it means the request itself was wrong — and
credits_exhausted is not retried either, since it is a balance rather than a
window. The full matrix, including where this client and the Python one differ,
is in the error reference.
A
503 arriving without a requestId may be a gateway timeout whose body was stripped
in transit. Report it with a timestamp rather than treating it as a malformed response.Browser usage
The package runs in the browser so it can be used in trusted contexts such as an internal tool behind auth, or a browser extension. For a public web app, keep the key on your server:Next steps
Python SDK
The same surface, for Python.
API overview
Every endpoint, request shape, and error code.