> ## Documentation Index
> Fetch the complete documentation index at: https://docs.memory.anonalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> What shipped in Anona Memory, newest first.

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](https://docs.anonalabs.com/changelog/rss.xml).

**October 2026**

### One sign-in for the CLI and MCP

`anona login` now asks for one approval instead of two. pip `anona` 0.26.1 and
npm `@anona-labs/memory` 0.20.1 store a single login that the data commands and
`anona mcp` both use, so a person signs in once. `anona login --mcp` still asks
for a separate MCP-only login, which `anona mcp` uses only when there is no
command-line login. An existing MCP-only login keeps working. See
[Signing in](https://docs.memory.anonalabs.com/cli/sign-in.md#with-anona-login).

### The CLI now covers the rest of the API

pip `anona` 0.26.0 and npm `@anona-labs/memory` 0.20.0 add a command for nearly
every remaining API route:

* **Per-space config:** `models` (list, create, edit, refresh, clear, history,
  delete), `profile set` and `profile reset`, `rules`, `webhooks` (including
  `deliveries`), `routing-settings`, and `spaces members list` (needs
  `anona login --cli`).
* **Explore:** `graph` (and `graph draw`), `entities`, `receipts get`,
  `ask-user`, `chat` and `space-types`.
* **Account:** `org`, `orgs`, `projects`, `keys`, `members`, `invitations`,
  `billing`, `exports` and `usage --history` / `--breakdown`. These read the
  control plane, which a CLI login from `anona login --cli` can now call on a
  fixed set of read routes. An API key or temporary profile gets
  `session_required` (exit `4`).
* Creating or revoking a key, changing plan, renaming or deleting an
  organization and inviting or removing members, of the organization or of a
  space, stay in the dashboard. Those
  commands print the dashboard link and exit `4` without sending anything.

See [the CLI reference](https://docs.memory.anonalabs.com/cli/account.md) and
[Authentication](https://docs.memory.anonalabs.com/api-reference/authentication.md#which-credential-each-route-accepts).

### Save a session on demand: `/anona-memory:save`

Ask the agent to save or export the session, or run `/anona-memory:save` in
Claude Code (plugin `0.4.0`), and it records every durable fact the session
settled, one memory each, then tells you how many it wrote. Add a topic to save
only part of it: `/anona-memory:save the deploy decisions`. An exported
conversation works the same way: hand it over and ask the agent to save what is
in it. See [Saving a session](https://docs.memory.anonalabs.com/integrations/skills.md#saving-a-session).

### Claude Code hooks keep Anona in sync, no API key needed

The Claude Code plugin (`0.3.0`) now runs three hooks on its own, covering the
moments the agent gets no turn:

* **Session start:** recalls what your space knows about the repository, branch
  and recent commits, and hands it to the agent as untrusted background data.
  It runs in the background, so startup never waits.
* **Before compaction:** sends a redacted excerpt of the conversation about to be
  summarised away (your prompts and the agent's replies, never tool output), so
  its decisions and fixes are kept.
* **Session end:** records a short work log: the task, branch, files changed and
  commits.

They use `ANONA_API_KEY` when it is set, and otherwise the login the `anona` CLI
already holds, through `anona mcp`. Reads route automatically when you have more
than one space. Writes go only to a space that is your own. Update with
`/plugin marketplace update anona`, then restart Claude Code. Installs from the
installer or `npx skills` can add the hooks by hand. See
[Hooks](https://docs.memory.anonalabs.com/integrations/skills.md#hooks).

### The onboarding prompt no longer plants an example memory

The onboarding prompt's setup check used to record "The user prefers pnpm." into
the new profile's real memory. An agent following it literally stored a
preference you never stated. The check now records that the setup check passed,
and the page says its other example sentences only show the shape of a call.
`anona start` also no longer says "Nothing warns you before then": the CLI
does remind you in the last 24 hours. In pip `anona` 0.25.4 and npm
`@anona-labs/memory` 0.19.4.

### Clearer CLI messages, and an unknown model suggests the right one

* **Unknown model names:** a misspelled model name in `chat-settings`,
  `reason-settings` or any API field now suggests up to three close matches and
  points to `GET /v1/models`, instead of listing every model.
* **Unknown commands:** an unknown `anona` command now says your copy may be
  older than the docs and gives the upgrade command.
* **Claiming:** `anona login --help` explains that approving claims a stored
  temporary profile.
* **`anona status`:**
  * It says the MCP token renews itself, and that you only need `anona login`
    again after a revocation or 30 unused days.
  * With an API key, it says the key is not checked here (`anona doctor`
    checks it).
* **Onboarding prompt:** agents now read credits with `anona usage --json`
  instead of taking the token out of `~/.anona/credentials.json`.

In pip `anona` 0.25.3 and npm `@anona-labs/memory` 0.19.3.

### `member_id=me` in a personal space matches every memory

`member_id=me`, on the memory list and on retrieve, used to return nothing in
a personal space. That is because only spaces shared across organizations
record who wrote each memory. In a personal space every memory is yours, so
`me` there now adds no filter. A named member id is still applied as given.
The CLI's `--member-id me` follows, and `anona guide` now lists the exit codes
for `anona status`, a second `anona start`, `anona logout`,
`job_not_cancellable` and `unsupported_model`. In pip `anona` 0.25.2 and npm
`@anona-labs/memory` 0.19.2.

### Clearer errors for tag filters and missing spaces

A malformed `tag_groups` filter now gets one error per mistake, with a field
path made of the fields you sent (`tag_groups.0.and.1.match`). Before, one bad
group could come back as eight errors whose path included internal validator
names. A group that isn't an object gets a message listing the four valid
shapes. `GET /v1/spaces/{id}/profile` on a space that doesn't exist now returns
`404 space_not_found`. It used to return `model_error` with a garbled message.

### 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](https://docs.memory.anonalabs.com/integrations/skills.md).

### `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](https://docs.memory.anonalabs.com/cli/sign-in.md#with-anona-login).

### 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](https://docs.memory.anonalabs.com/cli/overview.md#what-each-package-has).

### `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](https://docs.memory.anonalabs.com/cli/memory.md).

### `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](https://docs.memory.anonalabs.com/cli/sign-in.md#with-an-api-key).

### 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](https://docs.memory.anonalabs.com/agents/temp-profiles.md).

### 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](https://docs.memory.anonalabs.com/guides/scoping-multi-tenant.md#what-strict-means-in-practice)
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](https://docs.memory.anonalabs.com/cli/memory.md).

### 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](https://docs.memory.anonalabs.com/guides/plans-and-billing.md#credit-top-ups)
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](https://docs.memory.anonalabs.com/api-reference/billing.md#top-up-packs).

### Agent Skills for OpenClaw

The [skills installer](https://docs.memory.anonalabs.com/integrations/skills.md) 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](https://docs.memory.anonalabs.com/mcp-integration.md#openclaw) 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:

```
Author: bob
Merged by: alice
```

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:

```bash theme={null}
curl "https://api.anonalabs.com/v1/spaces/my-space/memories?repo=1282949371&merged_by=9001" \
  -H "Authorization: Bearer anona_live_YOUR_KEY"
```

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](https://docs.memory.anonalabs.com/github.md#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](https://docs.memory.anonalabs.com/api-reference/memories.md#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](https://docs.memory.anonalabs.com/api-reference/artifacts.md).

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](https://docs.memory.anonalabs.com/guides/charts-diagrams-artifacts.md).

### 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](https://docs.memory.anonalabs.com/api-reference/draw.md).

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](https://docs.memory.anonalabs.com/api-reference/organization.md#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](https://docs.memory.anonalabs.com/api-reference/chat.md) 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](https://docs.memory.anonalabs.com/api-reference/responses.md) 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`](https://docs.memory.anonalabs.com/api-reference/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](https://docs.memory.anonalabs.com/api-reference/memories.md), 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](https://docs.memory.anonalabs.com/api-reference/retrieve.md#source-passages) 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](https://docs.memory.anonalabs.com/api-reference/model-selection.md) 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](https://docs.memory.anonalabs.com/integrations/skills.md#compaction)
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](https://docs.memory.anonalabs.com/integrations/skills.md#hooks) has the snippet.

**September 2026**

### 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](https://docs.memory.anonalabs.com/guides/automatic-routing.md#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](https://docs.memory.anonalabs.com/legal/subprocessors.md) - 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](https://docs.memory.anonalabs.com/guides/automatic-routing.md) and
[Subprocessors](https://docs.memory.anonalabs.com/legal/subprocessors.md).

### 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:

```json theme={null}
{
  "name": "Never call revenue committed",
  "content": "Never describe revenue as committed unless a memory records a signed document. Name the stage the deal is actually at instead.",
  "priority": 100
}
```

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](https://docs.memory.anonalabs.com/api-reference/spaces.md#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:

```json theme={null}
{
  "name": "halcyon-account",
  "space_type": "sales_account"
}
```

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:

```json theme={null}
{
  "mission": "This space tracks the Halcyon account for the customer-success team; answers are read by non-technical account managers.",
  "disposition": { "skepticism": 2, "literalism": 2, "empathy": 5 }
}
```

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](https://docs.memory.anonalabs.com/api-reference/retrieve.md) result and every row of the
[memory list](https://docs.memory.anonalabs.com/api-reference/memories.md), in the same shape on both:

```json theme={null}
{
  "memory_id": "mem_abc123",
  "content": "The ingestion workers run on Kubernetes.",
  "tags": ["team:core", "name:kubernetes", "state:current"]
}
```

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.

```json theme={null}
{
  "tag_groups": [
    { "tags": ["name:typsecript"], "resolve": "fuzzy" }
  ]
}
```

`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](https://docs.memory.anonalabs.com/api-reference/retrieve.md#matching-a-name-you-are-not-sure-of).

### 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](https://docs.memory.anonalabs.com/api-reference/extraction-settings.md#label-taxonomy): 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`](https://docs.memory.anonalabs.com/api-reference/retrieve#compound-tag-filters) filters on it like
any other.

```json theme={null}
{
  "labels": [
    {
      "key": "name",
      "type": "multi-text",
      "tag": true,
      "description": "Every name the subject of this memory is known by, including abbreviations and short forms."
    }
  ]
}
```

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](https://docs.memory.anonalabs.com/api-reference/extraction-settings.md) 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](https://docs.memory.anonalabs.com/api-reference/memories.md). 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`](https://docs.memory.anonalabs.com/api-reference/retrieve#compound-tag-filters) takes an expression
instead. Groups are AND-ed; each is a leaf or one of `and`, `or`, `not`, nested
as deep as you need:

```json theme={null}
{
  "space_id": "support",
  "query": "what is left to do",
  "tag_groups": [
    { "or": [{ "tags": ["project:alpha"] }, { "tags": ["project:beta"] }] },
    { "not": { "tags": ["status:archived"] } }
  ]
}
```

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](https://docs.memory.anonalabs.com/api-reference/retrieve.md#compound-tag-filters), on
[reason](https://docs.memory.anonalabs.com/api-reference/reason.md), on the `retrieve` tool of the
[MCP server](https://docs.memory.anonalabs.com/mcp-integration.md), and on the
[memory listing](https://docs.memory.anonalabs.com/api-reference/memories.md), 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](https://docs.memory.anonalabs.com/sdk/javascript.md) was missing 25 of the client's 52
methods, including extraction settings, chat defaults, memory models, the model
catalog and webhooks; the [Python reference](https://docs.memory.anonalabs.com/sdk/python.md) 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](https://docs.memory.anonalabs.com/api-reference/overview.md) also described extraction settings
with field names the API does not accept. The fields are `mode`, `guidance` and
`custom_prompt`, as the [extraction settings](https://docs.memory.anonalabs.com/api-reference/extraction-settings.md)
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](https://docs.memory.anonalabs.com/api-reference/model-selection.md)

### 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.

```bash theme={null}
curl -fsSL https://raw.githubusercontent.com/anonalabs/Anona-Memory-SDK/main/skills/install.sh | bash
```

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](https://docs.memory.anonalabs.com/integrations/skills.md)

### 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](https://docs.memory.anonalabs.com/api-reference/context-receipts.md)

### 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](https://docs.memory.anonalabs.com/slack.md)

### 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](https://docs.memory.anonalabs.com/api-reference/documents.md)

### SDKs

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

[Temporal search](https://docs.memory.anonalabs.com/guides/temporal-search.md)

### 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](https://docs.memory.anonalabs.com/api-reference/feedback.md)

### 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.

**August 2026**

### 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](https://docs.memory.anonalabs.com/guides/scoping-multi-tenant.md)
* 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](https://docs.memory.anonalabs.com/guides/temporal-search.md)
* Edit or invalidate a single memory with `PATCH`.
  [Memories](https://docs.memory.anonalabs.com/api-reference/memories.md)
* 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](https://docs.memory.anonalabs.com/api-reference/user-profiles.md)
* Per-space extraction settings, so a space can be told what is worth
  remembering. [Extraction settings](https://docs.memory.anonalabs.com/api-reference/extraction-settings.md)
* Export a space and download everything in it. [Export](https://docs.memory.anonalabs.com/guides/export.md)

### 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](https://docs.memory.anonalabs.com/api-reference/documents.md)

### Retrieve and the drop-in proxy

* `format="block"` returns the assembled context as a string, ready to paste into
  a prompt. [Retrieve](https://docs.memory.anonalabs.com/api-reference/retrieve.md)
* A context receipt says what entered the prompt, what was cut and why.
  [Context receipts](https://docs.memory.anonalabs.com/api-reference/context-receipts.md)
* `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](https://docs.memory.anonalabs.com/guides/migrating-to-proxy.md)
* Choose the model that answers `reason` and chat, from a published catalog.
  Every model is available on every plan.
  [Model selection](https://docs.memory.anonalabs.com/api-reference/model-selection.md)

### 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](https://docs.memory.anonalabs.com/api-reference/api-keys.md)
* 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](https://docs.memory.anonalabs.com/sdk/javascript.md)
* Memory adapters for six Python agent frameworks: LangChain, CrewAI,
  LlamaIndex, Google ADK, Microsoft Agent Framework and Strands.
  [Integrations](https://docs.memory.anonalabs.com/integrations/langchain.md)
* 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](https://docs.memory.anonalabs.com/guides/plans-and-billing.md)
* Starter and Scale are no longer sold. Existing subscriptions keep their
  allowance and their price.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.