Skip to main content
Anona Memory ships continuously, so there are no version numbers to follow. This page groups each month’s customer-facing changes. Internal work, infrastructure and tests are left out. Subscribe to the feed at docs.anonalabs.com/changelog/rss.xml.

The agent skills use the anona CLI

When an agent has no MCP tools, the anona-memory skill now reaches memory through the anona CLI before falling back to curl: it runs anona status once, then retrieve, record and reason with --json, and acts on the exit code (3 means nothing was stored, 4 means no usable credential). The CLI uses an API key, the command-line login from anona login, or a temporary profile, so a machine signed in with anona login gets memory without a key in ~/.anona/config.env. The anona-memory-sdk skill now says which credentials work in code and which only work in the CLI. Needs pip anona 0.24.0 or later. See Agent Skills.

anona login signs the command line in too

anona login now asks for two approvals in the browser, one after the other: one for the command line and one for MCP, each stored in its own file and renewed on its own. record, retrieve, reason and the other data commands use the command-line one when no API key is set, so a signed-in machine no longer needs a key for them; they still need a space named, as with a key. anona mcp keeps using the MCP credential, unchanged. anona login --cli and anona login --mcp ask for one only. anona logout now also revokes what it deletes, and takes --cli or --mcp; anona status reports both. If one approval fails the other still runs. In pip anona 0.24.0 and npm @anona-labs/memory 0.18.0 (login, logout and status only). See the CLI.

The anona CLI reads and manages spaces, memories and jobs

The pip CLI now covers more of the API, with the same API key or temporary profile as record, retrieve and reason: anona spaces list|create|get, anona memories list|edit|delete|history, anona jobs get|cancel, anona profile, anona model, anona user-profile and anona usage. Every one takes --json, and the plain output shows full ids so the next command can use them. memories delete and jobs cancel do nothing without --yes: they exit 2 and say what would have been deleted. In anona 0.23.0. See the CLI.

anona record, retrieve and reason take --json and the API’s request fields

All three CLI verbs now take --json, which prints the API response as one JSON document on stdout and nothing else; on a failure stdout stays empty and the error goes to stderr. reason --json includes sources, what the answer was built from. The exit codes are unchanged, so a record that stored nothing still exits 3. The verbs also take the API’s request fields as flags: record reads content from stdin (-) or --file, and takes --tags, --timestamp, --metadata and the scope ids; retrieve takes --tags, --tags-match, --as-of, --occurred-after and --occurred-before; reason takes --depth and the scope ids. A flag you leave out is left out of the request, so existing commands send exactly what they did. In anona 0.22.0. See the CLI.

anona record, retrieve and reason take an API key

The three CLI verbs that call the memory API directly used to work only on a temporary profile. They now take an API key, from --api-key, ANONA_API_KEY or ~/.anona/config.env, in that order, so an account can use them without MCP. With a key, name the space with --space or ANONA_SPACE_ID; nothing is picked for you. A key from the environment or config.env goes only to Anona’s own servers unless the server is named in the same place, so a stray --base-url cannot carry it off. anona login stays the credential for MCP. A missing or refused credential now exits 4. In anona 0.21.0. See the CLI.

A temporary profile can no longer gain a second space by typo

A temporary profile holds exactly one space, default, but a write naming any other space used to create it: anona record "..." --space cedar-typo answered Stored in space 'cedar-typo'. and left the profile with two. Writes to any other space, and POST /v1/spaces, are now refused with 403 temp_profile_single_space, on every surface: the CLI, REST, uploads and MCP. Writes to default are unchanged. See temporary profiles.

An agent- or session-scoped memory list no longer comes back empty

A note is scoped to the user its facts are about, not to the agent or session that wrote them. The memory list hides a fact once a note covers it, so GET /v1/spaces/{id}/memories?agent_id=... used to return neither the fact, because it was covered, nor the note, because the note has no agent scope. A list filtered by agent_id, session_id or member_id now returns the facts. Scoping now explains which scope a note carries.

anona retrieve speaks JSON and takes the API’s filters

anona retrieve --json prints the retrieve API’s response as one JSON document (ids, scores, dates and scope on every row), so a script or an agent can cite, dedupe or delete what it found. --limit, --min-score, --user-id, --agent-id and --session-id pass straight to the API, and plain output now shows each memory’s score and short id, so a weak match no longer looks like a strong one. In anona 0.20.5. See the CLI.

The CLI no longer sends your credential to a server you name

anona mcp, record, retrieve and reason used to follow --base-url or ANONA_BASE_URL to any server, plain HTTP included, and send it the stored credential. They now talk only to the server that issued the credential, and refuse a different one with a message naming anona login --base-url, which is how you switch. Every command also requires https://, except for localhost and 127.0.0.1. Fixed in anona 0.20.4 and @anona-labs/memory 0.17.1: upgrade with pip install -U anona or npm install -g @anona-labs/memory@latest.

Top-up purchase history, and a billing guide for top-ups

The billing page now shows your plan allowance and purchased credits separately, and lists past top-ups under the packs, marked when a pack was refunded. The same list is available from GET /v1/billing/topups. Plans and billing has a new section on who can buy, spend order, refunds and low-balance emails.

Buy extra credits on a paid plan

Paid plans can buy credit packs of 5,000, 25,000 or 100,000 credits at $1.50 per 1,000, from the billing page or with POST /v1/billing/topup. Purchased credits are used after the plan allowance and never expire. See Billing.

Agent Skills for OpenClaw

The skills installer now installs into OpenClaw, into ~/.openclaw/skills or $OPENCLAW_STATE_DIR/skills. It is picked up automatically when OpenClaw is present, or with --app openclaw. The MCP page shows how to register the server with an OpenClaw node.

GitHub captures now say who wrote it, who merged it and who commented

A memory captured from a merged pull request names the people in it. The text Anona stores opens with the author and the merger:
and the discussion that happened in plain pull request comments is captured alongside the review conclusions, each comment attributed to whoever wrote it. Before this, a capture carried the title, the body and the review verdicts, so “who decided this?” had no answer in the memory at all.The same trust rule the connector already applied to a pull request body applies to each comment: it is captured only when GitHub says the person who wrote it is the repository’s owner, a member of the organization or a collaborator. An outside contributor’s prose is left out, as are bots. A long thread is trimmed to the newest comments that fit, whole comments only, and the capture says how many were left out.Provenance gained a second field to match. anona:author: now means the person who wrote the pull request, and the new anona:merged_by: means the person who merged it — usually two different people. Both come back in provenance on a retrieve result and on a memory list row, and both are selectable:
One caveat on history: until now author was stamped from whoever clicked merge, so memories captured before this change carry the merger’s id under author and no merged_by. Those rows are not rewritten, so a filter on author over a repository’s older captures is matching mergers. Captures from here on carry both fields, each meaning what it says. Imported history carries author but no merged_by — GitHub’s pull request list does not say who merged each one.See Where a memory came from.

A queued job now says why it is waiting

A job that had not started yet reported status: "pending" with progress: null and error: null, and nothing else. That one state covered three different situations — queued behind other work, parked until a later time, or already failed and requeued — so a job waiting on a transient problem was indistinguishable from one simply taking its turn, and the only way to find out was to keep polling.Job status now also returns retry_count (how many attempts the job has burned) and next_retry_at (when the worker will try it next). 0 with null means queued and awaiting pickup; a future next_retry_at means the job is parked on purpose and will resume on its own.A long-pending job is still never auto-failed on a deadline — a large batch legitimately queues for tens of minutes, and a timeout would report healthy work as data loss. Both fields are additive, so nothing else about the response changes.

Shareable artifacts

POST /v1/spaces/{space_id}/artifacts builds a self-contained web page that gives an overview of a space, in the background, and returns a stable link. Artifacts start private; the creator can share one with everyone who can read the space, and the space owner can make it public. Links open at /a/{id} on the dashboard, in a sandboxed frame, and public ones need no sign-in. A build is billed for the tokens it uses plus a flat amount, and a failed build costs nothing. See Artifacts.In the dashboard, type /artifact in a space’s Ask tab. Everything you have built is listed in the space’s Artifacts tab, where you can change sharing, copy the link or delete it. See Charts, diagrams and artifacts.

Charts and diagrams from your memories

POST /v1/spaces/{space_id}/graph draws from what a space holds. Ask in plain language and get back either a chart (bar, line, area, pie or scatter) or a Mermaid diagram (flowchart, sequence, state, mind map or timeline), plus copyable Mermaid source and a short note. Every number, step and label comes from the space’s memories; when they don’t describe what you asked for, you get a sentence saying so and nothing is drawn. Billed as one chat turn, and open to read-only members, since nothing is written back. See Draw.In the dashboard, type /draw in a space’s Ask tab (or / to see the commands), or just ask for “a pie chart of…” or “a flowchart of…”: the answer is drawn in the conversation with a Copy Mermaid button.

Who manages Slack and GitHub in a shared organization

Once shared organizations are enabled, installing or disconnecting the Slack or GitHub integration is the organization owner’s alone (403 integration_owner_only for anyone else), and binding a channel or repository, unbinding it and starting a GitHub backfill need an owner or admin (403 slack_admin_only / 403 github_admin_only). See Slack and GitHub integrations.Nothing changes before then: owners and admins install and disconnect, and any member can bind, unbind and backfill, exactly as today.

A truncated reply now says so

finish_reason on the chat proxy carried the provider’s own stop reason on several models instead of the documented value. A reply cut off at the token limit came back as end_turn or max_tokens rather than length, so the one case the field exists to warn you about read as a reply that ended normally. Guardrail and safety stops arrived unmapped the same way.finish_reason now carries the documented values on every model and in both streaming and non-streaming replies: stop, length, tool_calls and content_filter. A reply that hit the token limit reads length. The responses proxy has no finish_reason field — it reports status instead, which is always completed.If you match on the raw provider spellings — end_turn, max_tokens, guardrail_intervened — match on the documented values instead. Code that already reads the documented values is unaffected, and nothing else about the response changes. /v1/messages is unchanged: it reports Anthropic’s own stop_reason vocabulary (end_turn, max_tokens, tool_use) and always did.

Long writes are stored in full

A single write longer than about 40,000 characters, such as a long transcript or a pasted document sent to record, was stored only up to roughly its first 30,000 characters. The rest was never read, and the write still reported success. Every part of a long write is now stored and searchable.Writes made before this fix keep what was stored at the time. If you sent long single writes, re-sending them restores the missing part.

Retrieve can return the passage behind each memory

A memory is a summary of what was said. Set include_source: true on retrieve and each result also carries source: the passage of the original write it was extracted from. Use it to quote or cite a memory, or when an exact number or phrase matters more than the summary.It is off by default, so existing calls are unchanged. Passages add context, often doubling a response’s size, so source_max_tokens caps the total. A synthesized note carries the passage of the facts it was built from.

86 more models, served through OpenRouter

The model catalog grows from 22 to 108. The new models are named openrouter/<vendor>/<model> - for example openrouter/anthropic/claude-sonnet-5.5, openrouter/openai/gpt-6-sol or openrouter/google/gemini-3.8-flash - and each also has a short name ending in -or. They work anywhere model is accepted: the chat, responses and messages proxies, /v1/reason, and the model picker on the ask tab.Every model on the list was invoked before it was added, and had to hold a tool call, return valid JSON and stream cleanly. Credits are derived from each model’s real per-token price, the same way as every other model, so GET /v1/models and the model selection table carry exact rates.Nothing changes for existing callers. The default model is still GPT-OSS 120B on the same host, the existing models keep their names and prices, and a model is only used when you name it. Responses keep the OpenAI shape: no provider-specific fields are added.

The agent skill keeps what compaction would drop

Compacting a conversation replaces it with a summary, and whatever the summary leaves out is gone. The anona-memory agent skill now treats that as a deadline. Tell the agent you are about to compact and it records what the session settled first; when a conversation resumes from a summary, it records the durable facts the summary kept before going back to the task.It records facts, one memory each, into the session’s usual space - never the transcript or the summary itself. The usual rules hold: no secrets, nothing already in the repository, nothing still a hypothesis.Claude Code gives an agent no turn before it compacts, so the Claude Code plugin (0.2.0) adds a SessionStart hook that fires after every compaction and prompts the second half automatically. Installs from the installer or npx skills can add the same hook to settings.json by hand; the skills page has the snippet.

A routed search can look in more than one space

Some questions are answered out of two spaces at once - “was Acme charged for a bulk import we ran ourselves?” needs the charge and the account it landed on. A routed POST /v1/retrieve now searches the spaces that look likely to hold the answer rather than only the single best one, and returns one list ranked across all of them. searched carries an entry per space, best first.max_targets caps how many spaces a routed read may search - on the request, or as an organization default under Profile -> Automatic search in the dashboard. It is a ceiling, not a count: only spaces that look likely are searched at all, so a question that plainly belongs to one space still searches one, and raising the cap does not widen every search. Each space searched is charged, which is what makes it a cost control rather than a relevance setting. Set it to 1 to keep a routed read to a single space.Results are ranked across the spaces rather than concatenated, so the leading memory need not come from the leading space. If one space fails while others succeed the read still returns what they found, and the failed space keeps its searched entry with reason: "search_failed".max_targets is on the MCP retrieve tool and in both SDKs. On MCP, when more than one space was searched each memory is labelled with the space it came from, because a merged answer without provenance gives a model no way to weigh it. Sending max_targets on a retrieve that names its space_id is 422 route_conflict; both SDKs refuse it locally, without a round trip.

Search without naming a space

POST /v1/retrieve now takes route: "auto" in place of space_id, so a caller that knows the question but not which space holds the answer can have the space picked for it. The MCP retrieve tool takes it too, and both SDKs now expose routing on record, record_batch and retrieve.The answer comes back as searched, naming the space that was searched, the confidence, and which stage decided: rule when one of your own rules matched, model when the decision model chose, fallback when a fallback_space_id you sent was used. Read it - a query answered from the wrong space looks exactly like a space that holds nothing, and this is the only field that distinguishes them. On a retrieve that names its space_id, searched is absent from the body entirely, so nothing changes for anyone not using routing.One behaviour differs from routed writes on purpose. When no space fits, a routed read returns an empty results array with the reason, and does not fall back to your default space. On a write the default space is the sink and filing there is right; on a read it is the bag of everything that fitted nowhere, so it is the worst place to look for a specific answer, and irrelevant memories are worse than none. fallback_space_id therefore has no default on a read: set it when you would rather search something than nothing. An unroutable query is still charged, like any retrieve that matches nothing.Routing is still opt in per request, and POST /v1/reason is unchanged: it requires a space_id. See Searching without naming a space.

Automatic routing is now switched on

route: "auto" has been accepted for a few days but was not yet enabled, so every routed write fell back to the default space and reported reason: "router_disabled". It now routes for real: a routed write is matched against your spaces and filed into the one it belongs in.Two consequences worth knowing. A routed write now sends the memory’s text, and the descriptions of the spaces it could go to, to the third-party decision model named in Subprocessors - read that page before using it if your organization cares where content goes. And nothing else changed: a write that names its space_id still sends nothing anywhere new, routing is still opt in on every individual request, and a space can be excluded from consideration entirely with auto_route_enabled: false.

Write a memory without naming a space

POST /v1/record and POST /v1/record/batch now take route: "auto" in place of space_id, and Anona files the memory into the space it belongs in. The MCP record tool takes it too, which matters most there: an agent sees a list of space ids and none of the reasoning behind them.The answer comes back as routed_to, naming the space, the confidence and which stage decided: rule when one of your own declared rules matched, model when the decision model chose, fallback when nothing claimed the memory and it went to your default space. A fallback write is marked "anona_routed": "fallback" in its metadata, so what needs filing by hand is something you can list.Each space gets an optional routing profile at /v1/spaces/{space_id}/routing-settings: a charter, topics, and rules that answer before any model is called. The description a space is judged on is built primarily from terms derived from the space’s own contents, and a charter adds to those terms rather than replacing them.Routing is opt in on every request. A write that names its space is unchanged, and sends nothing anywhere new. See Automatic space routing and Subprocessors.

GitHub can correct the sentences your knowledge file gets wrong

The knowledge pull request used to only write Anona’s own section, between the anona:begin and anona:end markers, and never touch a byte outside them. Corrections extends it: where your memory space contradicts a sentence you wrote, Anona proposes a rewrite of that sentence.It is off by default, and turning it on needs a knowledge file chosen first. A pass proposes at most three rewrites, each carrying the memories that contradict it — quoted, with their source — so a correction you cannot check is one you never have to merge. Everything else in the file is left byte for byte as it was.Each edit lands wrapped in an <anona> tag, and that tag is the opt-out: delete it and that sentence is never proposed again. Closing the pull request says no to everything it proposed, permanently. Corrections share the existing cadence and the one-pull-request-at-a-time rule, and a long file is walked six sections at a time across successive passes.

A job now says whether any of its content was silently lost

Extraction can come back with no facts at all for a chunk it was given, and the job used to answer completed, error: null regardless — the content simply gone, with nothing on the response saying so. A retry now runs once before that zero is accepted, which recovers most of them, and GET /v1/spaces/{space_id}/jobs/{job_id} gains zero_yield_items: how many of the job’s items_submitted produced nothing on both attempts. It is 0, not null, once a job completes — that distinction is the point, since a missing field used to be indistinguishable from a measured zero. Non-zero is worth re-submitting that content; it is not treated as a failed job, for the same reason a low memory_count against items_submitted isn’t — everything else in the batch is real and searchable.

Both SDKs can set a space’s reason depth, and can no longer clear it by accident

depth reached the API in the release that added it but never reached the SDKs. Two consequences, and the second was losing data.reason now takes it per call, in both clients — depth="thorough" checks the notes and the raw memories underneath instead of letting a current memory model answer on its own. It is the second look to ask for when an answer reads stale, and until now it was reachable only by hand-rolling the HTTP call.The setter is the part that was doing harm. PUT /v1/spaces/{space_id}/reason-settings is a full replace, and both SDKs sent only model — so a space whose owner had chosen "thorough" in the dashboard silently fell back to "fast" the next time anything pinned its model through an SDK. Cheaper, shallower answers, with nothing to say why.set_reason_settings / setReasonSettings now require both model and depth. A call that names one and not the other is an error at the call site rather than a setting the customer loses quietly. If you are changing one, read the current pair back with get_reason_settings / getReasonSettings first.The Python client also gained reason_receipt (and async_reason_receipt), which returns the same answer plus what it was built from: sources says which layer spoke — memory models, raw memories or consolidated notes — alongside rules_applied and the model that actually ran. reason itself still returns the answer string, so nothing that reads it needs to change. In TypeScript, sources and model are now declared on InsightsResult; they were already on the wire.

A space can be given rules it must follow

POST /v1/spaces/{space_id}/rules adds a standing instruction the space obeys whenever it answers:
A rule is not a preference and not a tone setting — those are the disposition dials on the space profile, which shift how an answer is worded. A rule applies to every answer the space gives and it overrides what the memories say: the one above holds even when a memory records the deal as done. Until now the only way to get that was to repeat the instruction in every prompt, and any caller who forgot it got a different answer from the same space.GET lists them, highest priority first, with ties broken by recency. PATCH is a partial edit, so switching one off or lifting its priority never means resending the text of something enforced on every answer. DELETE removes it. All of it is owner-only and free — an organization out of credits must still be able to switch off a rule that is producing bad answers.A space may have 25 active rules at once. Every active rule is written into every answer the space composes, so the cap is what stops a space from spending more of an answer on its own rules than on its memories. It counts active rules only, so {"is_active": false} always frees a slot without deleting anything.And an answer now says which rules shaped it. POST /v1/reason returns rules_applied — the id and name of each rule that applied, and deliberately not its text, which is your own configuration and already in hand. An empty list is the useful half of that: it says no rule applied, which is what separates “the memories are wrong” from “a rule said otherwise”. Those need opposite fixes and read identically in the prose.Both SDKs wrap it: list_rules / create_rule / update_rule / delete_rule in Python, listRules / createRule / updateRule / deleteRule in TypeScript. Full detail in Rules.

A space can be created knowing what it is for

POST /v1/spaces takes space_type, and sets the new space up for that job:
Five to choose from — customer_support, coding_agent, personal_assistant, sales_account and blank. GET /v1/space-types lists them with what each one sets up, and needs no API key.A type writes three things: the space’s mission, the guidance its extraction pass follows, and two memory models — the standing questions that kind of space usually wants an answer to. All three have been settable one endpoint at a time for a while, and a space that never had them set ran general-purpose defaults forever — which is what most spaces did.The two models start empty, and that is deliberate. Each carries refresh_on_new_memories, so it writes itself once the space has memories worth reading, and rewrites itself as the space learns more. Generating at creation would have spent your credits to store “I have no information about this” as the model’s content — and that sentence then reads as the answer until something replaces it. Record something, let it consolidate, and read the models back; or refresh one by hand if you would rather not wait.Everything a type sets stays editable: PUT /v1/spaces/{space_id}/profile for the mission and dials, PUT /v1/spaces/{space_id}/extraction-settings for the guidance, and the models API for the models. A space type is a starting point, not a mode. Omitting space_type creates exactly the space this endpoint created before, and an unrecognised value is a 422 rather than a plain space you did not ask for.The response echoes space_type back, and it is null whenever nothing was applied — including when a type was asked for and applying it failed. Creating the space never fails over a preset, so read that field rather than assuming the type you sent is the type you got.

Answers say what they were built from

POST /v1/reason now returns sources — which memory models, how many raw memories and how many notes the answer was built on. GET /v1/spaces/{space_id}/models/{model_id} returns the list itself, with each source’s id, kind and text.Until now a wrong answer gave you prose and nothing else, and there was no way to tell the memory is missing from the memory is there and was not used — which need opposite fixes. These are the memories the pass declared it used, so that distinction is now readable: if what you expected is absent it was never reached; if it is present and the answer contradicts it, that is worth reporting to us.A non-empty sources.models means a standing answer spoke, which is the first thing to check when a result looks out of date — refresh that model, or ask again with depth: "thorough".The MCP reason and get_model tools state the same thing in a closing line, and the dashboard shows “Built from …” under a model’s answer.

A space’s mission and disposition can be set

The Models tab has always opened with a Space profile card — a mission line and three dials, skepticism, literalism and empathy — and there was no way to change any of it. Every space showed the same card with the same numbers.PUT /v1/spaces/{space_id}/profile now sets both, and the card has an edit control:
The mission is the more useful half: it is written into every Ask answer and every model refresh, so the framing does not have to be repeated in each question. The dials (1-5, default 3) change how the space weighs what it is told when it synthesises; they do not affect plain retrieval.Setting a profile is free and owner-only — a space shared with you can be read and written to, but not reconfigured. The PUT replaces the whole profile, so send both halves, and DELETE puts it back to no mission and 3/3/3.

Reason can be told to look past its own summary

POST /v1/reason takes depth: fast, which is what every call has done until now, or thorough.Reason keeps a layer of standing answers — memory models — above your notes and raw memories, and on fast a model that is current answers on its own. That is what makes a typical call quick, and it is also why a stale model made every answer built on it stale, with no way to ask for a second look.depth: "thorough" reads the notes and the raw memories underneath whatever the models say. It takes longer and uses more tokens, and it is charged on the tokens it actually uses rather than at a different rate.A space can make it the default with PUT /v1/spaces/{space_id}/reason-settings ({"model": null, "depth": "thorough"}) — a depth on the request still wins. Note that this PUT replaces the whole resource, so send both fields. The MCP reason tool and POST /v1/spaces/{space_id}/users/{user_id}/ask take depth too, and all three read the same space default.

Deleting a memory now deletes it

DELETE /v1/spaces/{space_id}/memories/{memory_id} answered 204 without removing the memory. It cleared the observations consolidated from that memory and reset the memory for re-consolidation — so the memory stayed in the space, and was synthesised into fresh observations on the next consolidation run.It deletes now: the memory, its links, its entity associations, and every observation built from it, with the other memories that fed those observations re-consolidated without it. There is no undo.If you used this endpoint, re-run those deletions. Anything deleted before this shipped is still in your space and will still be returned by GET /v1/spaces/{space_id}/memories and by retrieve.Two smaller changes ride with it. A memory id the space does not hold is now 404 memory_not_found instead of a success, so deleting the same memory twice reports the second attempt honestly. And where a delete cannot be confirmed, the endpoint returns 503 delete_unavailable and states that the memory was not deleted, rather than reporting one it did not make.To take a memory out of recall while keeping it for audit, PATCH it to state: "invalidated" instead — that is reversible, and it is unchanged.

A faster host for a model you already have

gpt-oss-turbo joins the catalog. It is the same 120B model as the default gpt-oss, served by Cerebras instead of Bedrock — measured several times faster per call on the tool-calling shape reason and agents send, at 2.02x the credits. Both stay: if latency is not your problem, you should not pay for it.Choose it per request with "model": "gpt-oss-turbo", or as a space default on the chat defaults tab. Credit rates are derived from the host’s real per-token price like every other model, so nothing about how you are billed changes — the rate is on GET /v1/models and in the pricing table.Two new fields ride on each GET /v1/models entry. provider names the host, because the same weights can now appear twice. supports_memory_operations is false for a model that answers chat but not /v1/reason — gpt-oss-turbo is chat-only today, and the dashboard’s reason picker hides it rather than offering a choice that would be refused. Naming it on a memory call returns 400 unsupported_model listing what can be used instead; nothing is ever silently substituted.

Labels are stamped, and you can see them

Two fixes that belong together, because the second is why the first took so long to find.A label group is now always filled in. A group the extraction model was free to skip was a group it skipped: measured across a corpus, a multi-text group stamped nothing on any memory while a value group marked required stamped on every one. Adding the first alongside the second stopped both. Every group is now answered, with an empty value saying “none” rather than the answer being left out. optional keeps its meaning and does the job it should always have done: an optional group may answer null or [], and optional: false on a list now requires at least one value, where before it was read and ignored.If you configured a taxonomy and saw no tags on your memories, that is this, and it applies to writes made from now on. Existing memories are never re-extracted, so a space that ingested under the old behaviour needs re-ingesting to carry the tags it should have.A memory now returns its own tags. tags is on every retrieve result and every row of the memory list, in the same shape on both:
These are exactly what tags and tag_groups match on, so this is how you check what a filter will select before you rely on it. It is also the only way to see whether a label taxonomy is doing anything: until now a group that stamped nothing looked identical to one that worked.Scope is not included, and that is deliberate: user_id, agent_id, session_id and member_id carry it as fields, as they always have.

A tag filter can match a name you half remember

tag_groups matched tag strings exactly, which is the right behaviour when your own code wrote the tags and the wrong one when it did not. An agent building a filter out of something a user typed had to know the exact string a memory was stamped with, and one transposed letter meant the memory could not be reached at all.A leaf can now carry "resolve": "fuzzy". Its tags become candidates rather than requirements: each is compared against the tags your space already holds, and the close ones are added to the filter.
name:typsecript matches nothing on its own. Resolved, it also matches name:typescript.It only ever widens: the tags you named are always kept, so a fuzzy leaf matches at least everything the exact one would have. Resolution stays inside the tag’s namespace, so name:chekcout can reach name:checkout and never status:checkout. Candidates shorter than four characters are matched literally, because similarity on two or three characters is noise rather than signal.It fixes misspellings, not abbreviations: kubernetes and k8s have almost no letters in common, so nothing resolves one to the other. That is what the multi-text label group above is for, and the two work together, the taxonomy recording every name on the way in and the filter forgiving how it is typed on the way out.Unset by default, so nothing changes for a filter that does not ask for it. Details.

Extraction can tag your memories for you

Filtering recall is only as good as the tags a memory carries, and until now every one of them had to be supplied by hand on the write that created it. Anything your own code did not think to tag was unfilterable forever, because nothing re-reads a stored memory.A space can now declare a label taxonomy on extraction settings: the dimensions that matter in your domain, described once. Extraction classifies every memory along them as it writes, and a group marked tag: true becomes a real tag, so tag_groups filters on it like any other.
A memory about Kubernetes then carries name:kubernetes, name:k8s and name:kube, and a query for any of the three finds it. multi-text is the type that makes that work: identity rarely has a single surface form, and a taxonomy whose vocabulary you cannot list in advance now has a shape that fits it. Four types in total, two with a fixed vocabulary you supply and two without.The settings are unset by default and nothing changes for a space that does not use them. Labels apply to writes made after you save them, so turning a taxonomy on never retags your history, and turning one off never loses it.One thing to know when you write a taxonomy: a group’s description is instruction text the extractor reads, so it should describe the label and not how to extract. A description that says what counts as a separate memory, or when to split one fact into two, is read as extraction policy and obeyed as one, and the result is fewer memories rather than an error. A multi-line description no longer escapes into the surrounding prompt, which is worth some of that effect but not most of it: the wording is. The extraction settings page has the worked example and the numbers.

Every ingest job says how many items it was given

A job reported how many memories it created and nothing to read that against. Fifteen memories is a good outcome for twelve items and a destroyed corpus for thirty-five, so items_submitted now sits beside memory_count on job status. It is the quickest check after changing extraction settings, which is the setting most able to change the ratio.A job whose two numbers are far apart still reports completed: the memories it wrote are real, and a batch of near-identical items legitimately yields fewer memories than items. The pair is the answer; what it means is yours to read. Progress counters on a running batch also now cover the whole job rather than whichever part reported last.

Filter recall with boolean tag expressions

tags is a flat list under one match mode, so it can say “any of these” or “all of these” and nothing else. There was no way to write an OR of groups, and no way to exclude anything at all.tag_groups takes an expression instead. Groups are AND-ed; each is a leaf or one of and, or, not, nested as deep as you need:
The filter is compiled into the search itself rather than applied to its output, so an excluded memory never competes for a slot and you get the best n matches that passed the filter instead of whatever survived it.A leaf may name up to 500 tags, and one expression up to 1,000 in total, well above the 50 that tags itself allows. That is deliberate: naming every candidate you might mean and letting the filter decide which of them your space actually has is the normal way to use this, and it is the cheap shape too, since one leaf is a single set-containment test however many tags it lists.It works on retrieve, on reason, on the retrieve tool of the MCP server, and on the memory listing, where it is JSON-encoded into the query string because that endpoint is a GET. It is in both SDKs too: tag_groups in Python, tagGroups in TypeScript, where it is typed so a malformed filter is a compile error.Scoping still holds. tags, user_id, agent_id and session_id are all AND-ed onto whatever you write, so a scoped query stays scoped when you add a filter, and a filter can never reach outside the scope it was asked in.

Every SDK method is in the reference

Both SDK pages had fallen behind the clients they document. The TypeScript reference was missing 25 of the client’s 52 methods, including extraction settings, chat defaults, memory models, the model catalog and webhooks; the Python reference was missing 20 of 54. Both now list every method the installed package exposes, so a capability you have been paying for is no longer invisible because only the other language’s page mentioned it.The published SDK README gained the context receipt methods (get_context, retrieve_receipt, get_receipt, explain) and create_space / delete_space, none of which it had ever listed.The endpoint index also described extraction settings with field names the API does not accept. The fields are mode, guidance and custom_prompt, as the extraction settings page has always said.

Fourteen more models, and a new default

The model catalog goes from seven to twenty one. New this month: Claude Haiku 4.5, Claude Sonnet 4.5 and 4.6, Claude Opus 4.5 and 4.6, GLM 4.7 Flash, GLM 5, Kimi K2.5, MiniMax M2.1, Mistral Large 3, Nemotron Nano 3 30B, Nemotron 3 Super 120B, GPT-OSS 20B and Llama 4 Scout.Every one of them was checked by running the real work against it, not by reading a spec sheet: a model is only listed here if it answered a live tool-calling request from this deployment and held the output contract while doing it. Several models that look fine on paper are absent for exactly that reason.GPT-OSS 120B is now the default, replacing Nova Pro. It matched Nova Pro on extraction quality in our own testing, and it carries twenty times the requests-per-minute headroom, so heavy periods are far less likely to queue. It is also more reliable on the write path: the occasional failed extraction that could surface as a 503 on a synchronous record came from the old default and is gone.Nova Pro has not gone anywhere. It stays fully selectable as nova-pro, and if you pinned it by name you are still on it.If you never set model, nothing changes for you. A record or a reason costs the same credits it did before, because the credit scale was recalibrated around the new default at the same time.If you did pin a model by name, its credit rate has changed. Costs are derived from each model’s real per-token price, and those are now expressed against GPT-OSS rather than Nova Pro. Nova Pro reads as 5.3x the default where it used to read as 1x. The dollars behind a credit did not move; what changed is which model sits at the baseline. Check GET /v1/models for the exact rate of whatever you pin, and prefer it to the multiplier when costing a real workload.Model selection

Agent Skills

Install once, and your coding agent uses memory without being told to. It recalls what is already known before a task and records what the task settled after it, in Claude Code, Codex, Cursor, OpenCode and Hermes.
A second skill covers building on Anona: the SDK and REST surface, spaces, per-user scoping and error codes, so code written against it matches what the API actually does.Claude Desktop and claude.ai take a ZIP upload rather than a directory. Pass --zip to build one, then add Anona as a connector so the skill has tools to call.Agent Skills

Context receipts

A receipt can now account for the cuts search makes internally, not only the ones made after it returned. Pass receipt_detail: "full" on retrieve and the manifest gains three reasons for memories that were ranked and dropped before your limit or min_score ever applied.There is also a way to ask about one specific memory: GET /v1/receipts/{request_id}/explain?memory_id=... replays that search, pinned to the moment it originally ran, and reports where the memory left the pipeline, which search methods found it, and what it scored. Its not_retrieved answer is the one that was previously impossible to get: no part of search matched the memory at all, so raising limit will not help and the scope or the wording is what to check. It works on any earlier call, not just ones that asked for a receipt in advance, and costs no credits.Context receipts

Slack

Anona answers in Slack from a bound space. It reads the thread it was mentioned in, not only the message, so a question that depends on what was said three replies ago still gets an answer.A conversation can also be saved into memory, either by asking the bot in the thread or from the message menu. The channel has to be set to Read & write first: a channel is read only by default, because saving changes what the space recalls afterwards. Saves are capped per workspace. Connecting your Slack identity to your Anona account attributes the memories you save to you.Slack integration

Uploads

Video is read into memory. MP4, MOV, WEBM and MKV, covering speech, on-screen text and a description of what is happening. Video gets a larger per-file ceiling than other uploads, since a few seconds of it outweighs a long document.Documents and uploads

SDKs

Both the Python and the TypeScript client can now filter on when something happened, not only on when it was recorded.Temporal search

API

An API key can file a feedback report. Before this it took a dashboard session, which an agent running on its own does not have.Feedback

Fixes

  • An ingestion that fails gives back the credit it charged to start.
  • A memory model keeps its answer when the model that writes it wraps that answer in an envelope of its own. The wrapper is unwrapped rather than stored.
  • amazon.nova-2-lite-v1:0 is billed at the cheap rate. It was falling through to the mid tier and collecting about 2.5 times what it was worth.
  • Creating a space that already exists answers 409 instead of failing with a server error, and uploading to a space that does not exist creates it.

Memory

  • Scope every write and read by user, agent and session, so one space can serve many end users without them seeing each other. Multi-tenant scoping
  • Ask what memory looked like at a past moment with as_of, and filter recall on when an event happened rather than when it was written. Temporal search
  • Edit or invalidate a single memory with PATCH. Memories
  • A write returns the ids of the memories it created, on both the synchronous and the asynchronous path.
  • A running ingestion job can be cancelled. Parts already in flight finish; the rest are dropped.
  • Per-user profiles, and a way to ask a question about one user. User profiles
  • Per-space extraction settings, so a space can be told what is worth remembering. Extraction settings
  • Export a space and download everything in it. Export

Uploads

Files are read into memory: documents, images (JPG, PNG) and audio (MP3, WAV). Each file is routed to a parser by its extension, per file rather than per request, so a mixed batch is handled correctly.Two limits arrived with them. A file whose bytes contradict its extension is rejected, and an archive that unpacks to far more than it weighs is refused before anything is unpacked. Documents and uploads

Retrieve and the drop-in proxy

  • format="block" returns the assembled context as a string, ready to paste into a prompt. Retrieve
  • A context receipt says what entered the prompt, what was cut and why. Context receipts
  • block_order="stable" holds the memory block in a stable order across turns so a provider’s prompt cache can hit it, with a meter reporting how much of the prefix was eligible.
  • The proxy covers three wire shapes: OpenAI Chat Completions, OpenAI Responses and Anthropic Messages. Migrating to the proxy
  • Choose the model that answers reason and chat, from a published catalog. Every model is available on every plan. Model selection

Spaces and access

  • A space can be shared, including with someone in another organization, who joins by invitation as a read only or a read and write member.
  • API keys can be created with an expiry (expires_in_days). An expired key is rejected with key_expired, which is distinct from an invalid one. API keys
  • Signup no longer mints an API key. Create one in the dashboard, or through the API, when you need it.
  • You can sign up with a social identity rather than a password.

SDKs and integrations

  • A TypeScript SDK. JavaScript and TypeScript
  • Memory adapters for six Python agent frameworks: LangChain, CrewAI, LlamaIndex, Google ADK, Microsoft Agent Framework and Strands. Integrations
  • Both SDKs reach per-space configuration and the per-user profile endpoints.

Dashboard

  • A new visual system across the whole dashboard and the transactional email.
  • The Ask tab is a multi-turn chat over a space’s memory, with a model picker.
  • A Users tab, a memory models tab, and a Profile page that owns the organization name.
  • Send us feedback without leaving the page you are on.

Billing

  • Stripe checkout, the customer portal and webhook settlement.
  • Plans repriced. Developer is the free tier at 5,000 credits a month. Plans and billing
  • Starter and Scale are no longer sold. Existing subscriptions keep their allowance and their price.