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
Anote 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 fromGET /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 withPOST /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: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: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 reportedstatus: "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. Setinclude_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 namedopenrouter/<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. Theanona-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 routedPOST /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 theanona: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 answercompleted, 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: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: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: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, amulti-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: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 markedtag: true becomes a real tag,
so tag_groups filters on it like
any other.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, soitems_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: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 a503 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 selectionAgent 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.--zip to build one, then add Anona as a connector so the skill has tools to
call.Agent SkillsContext receipts
A receipt can now account for the cuts search makes internally, not only the ones made after it returned. Passreceipt_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 receiptsSlack
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 integrationUploads
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 uploadsSDKs
Both the Python and the TypeScript client can now filter on when something happened, not only on when it was recorded.Temporal searchAPI
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.FeedbackFixes
- 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:0is 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
409instead 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 uploadsRetrieve 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
reasonand 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 withkey_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.