Skip to main content
Every write normally names its space. When the caller does not know which space a memory belongs in, send route: "auto" instead of space_id and Anona chooses one for you.
Routing is opt in, per request. A write that names a space_id behaves exactly as it always has: no routing runs, nothing extra is called, and routed_to is null.
Automatic routing is available on POST /v1/record, POST /v1/record/batch and the MCP record tool. The Python and JavaScript SDKs do not expose it yet, so call the endpoint directly for now.

Naming a target

Exactly one of space_id or route must be present. Sending both, or neither, is 422 route_conflict. Sending max_targets: 2 or 3 is 422 validation_error: the field is capped at 1, and a value the API would have to ignore is refused rather than accepted quietly.

Reading routed_to

POST /v1/record returns a routed_to array whenever route was used. It is null on a write that named its space. There are three stage values and no others: routed_to is the only way to notice a misroute. A memory filed into the wrong space looks exactly like a correct write from every other angle, so log it, or surface it, on any path where routing matters.
POST /v1/record/batch routes the whole batch as one unit and files every item together, but its response carries job_id, job_ids, status and accepted only. There is no routed_to on a batch response. Items cannot be routed individually.

The cascade

Routing runs three stages in order, and stops at the first that answers.
1

Declared rules

Your own rules are evaluated first, over your candidate spaces in deterministic order by space id. The first match wins. This costs nothing and calls no model.
2

The decision model

The memory text and each candidate space’s description are sent to a decision model, which picks one space or abstains. An abstention, or an answer below the confidence threshold, falls through. See Subprocessors for what leaves Anona on this step.
3

Fallback

fallback_space_id if you sent one, then your organization’s default_space_id, then a space literally named default if you have one. If none of those exist, the write is refused with 422 no_route_target.
A memory that lands on the fallback stage is stored with "anona_routed": "fallback" in its metadata, so you can list what needs filing by hand later. That mark is only trustworthy because you cannot write it yourself: metadata keys beginning with anona_ are reserved and rejected with 422 reserved_metadata_key. If you have no candidate spaces at all, no model is called and the memory goes straight to the fallback. An organization whose only space is its default is in exactly that position, and routing costs it nothing.

Which spaces can be chosen

A space is a candidate when all of the following hold:
  • It has a routing profile with auto_route_enabled set (the default when a profile exists).
  • Its rendered description is not empty. A space with nothing to say about itself does not compete.
  • It is not your organization’s default space. The default is the sink that receives everything unclaimed, so it is never also a target.
Spaces shared with you by another organization are candidates only when the owner set shared_auto_route on them and your membership role is developer. A read-only member can never be routed into, for the same reason they cannot write by hand. The router never creates a space. Every answer, whether from a rule or from the model, is checked against the candidate list that was sent before anything is written, so a name nobody offered is discarded rather than acted on.

What makes routing work well

The description a space is judged on is built primarily from terms derived from the space’s own contents. Anona reads the entity graph it already builds on every write, refreshes each space’s terms in the background, and adds the terms it sees on writes (tag names, agent_id values and the top level keys of metadata, never memory content and never user_id or session_id values). A charter you write adds to those terms, it does not replace them. The rendered description is your charter, then the terms. This is deliberate: a short charter on its own (“eng”, “ops”, “money”) describes a space far worse than its own contents do, so nothing you write can displace what was derived. Practical consequences:
  • A new, empty space is not routable yet. It has no contents to derive terms from. Write to it by name a few times, or give it a charter and topics, and it joins the candidate set.
  • Write a charter in sentences. It prefixes the terms, so a sentence about what the space is for adds information the terms cannot carry.
  • Use rules for the decisions that are not judgement calls. Anything keyed on agent_id, user_id, session_id or a tag is better as a rule: it is free, instant and exact.
Fan-out to several spaces, an inverted index over your memories, and a tool for calibrating the confidence threshold do not exist. max_targets is the field where fan-out would live, which is why it is capped at 1 today rather than absent.

Routing settings

Each space has an optional routing profile.
Authenticate with an API key or a dashboard session. You must be the owner of the space: a shared space’s visitor cannot change how it is routed into. These endpoints are configuration, not metered work, so they cost no credits and keep working when an organization is out of credits, which is what lets you turn routing off at any time. GET on a space that has never been configured returns the defaults rather than a 404. PUT additionally checks that the space really exists, so it needs an active API key on the organization and answers 404 space_not_found for a misspelled id instead of storing a profile nothing would ever read.

The settable fields

PUT is a full replace, not a patch. A field you omit is cleared, and an omitted flag returns to its default. Send the complete state you want.

Rules

Matching is case insensitive. in tests membership of the array in value; the other three operators given an array match if any element does. Regular expressions are not an available operator. A rule whose space_id names a space that is not a candidate is skipped, not obeyed. That includes your default space, which is never a routing target.

The derived fields

GET and PUT also return the server-owned half of the profile. These are read only: they are absent from the request body, and sending one is rejected. rendered_criteria is worth reading. It is what the decision model actually sees, so it is the thing to look at when a space is being chosen too often or never at all. DELETE removes the profile entirely, which takes the space out of routing until it is configured again. Deleting a profile that does not exist is still a 204.

Setting a default space

The fallback ladder’s second rung is your organization’s default space.
The id is checked against your own spaces, so a typo is a 404 space_not_found rather than a stored id that nothing can serve. Owners and admins only.

Errors

Errors use the standard envelope, with lowercase snake_case codes:
Branch on code. message is written for a human and its wording may change. Routing itself never breaks a write. A dead rule, an unreachable decision model or an unreadable answer all fall through to the fallback. no_route_target is the one exception, and it means there is genuinely nowhere to put the memory.