route: "auto" instead of space_id and Anona chooses
one for you.
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 ofspace_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.
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."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_enabledset (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.
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_idor a tag is better as a rule: it is free, instant and exact.
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.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
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.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:
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.