# Namespace routing

```json metadata
{
  "schema_version": 2,
  "title": "Namespace routing",
  "description": "Configure shared namespace routing and admission rules for long-term memories.",
  "categories": ["docs","develop","ai"],
  "tableOfContents": {"sections":[{"id":"understand-placement-and-admission","title":"Understand placement and admission"},{"id":"configure-a-policy","title":"Configure a policy"},{"id":"how-rules-are-evaluated","title":"How rules are evaluated"},{"id":"move-memories-with-the-policy","title":"Move memories with the policy"}]}

,
  "codeExamples": [{"codetabsId":"iris_agent_memory_namespace_routing-stepnamespace-routing-destination","difficulty":"beginner","id":"namespace-routing-destination","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-destination"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-destination"}]},{"codetabsId":"iris_agent_memory_namespace_routing-stepnamespace-routing-get","difficulty":"beginner","id":"namespace-routing-get","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-get"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-get"}]},{"codetabsId":"iris_agent_memory_namespace_routing-stepnamespace-routing-put","difficulty":"beginner","id":"namespace-routing-put","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-put"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-put"}]},{"codetabsId":"iris_agent_memory_namespace_routing-stepnamespace-routing-source","difficulty":"beginner","id":"namespace-routing-source","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-source"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-source"}]},{"codetabsId":"iris_agent_memory_namespace_routing-stepnamespace-routing-move","difficulty":"beginner","id":"namespace-routing-move","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-move"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-move"}]},{"codetabsId":"iris_agent_memory_namespace_routing-stepnamespace-routing-explicit","difficulty":"beginner","id":"namespace-routing-explicit","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-explicit"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_namespace_routing-stepnamespace-routing-explicit"}]}]
}
```

## Code examples

These examples connect to Iris services using SDKs or HTTP requests with curl. Follow one language throughout each walkthrough.


Namespace routing selects a shared namespace for an eligible memory. Admission rules check whether a memory can enter a destination. Use routing to organize shared memories by content, and admission rules to reject content that does not belong in a namespace.

The policy applies to one store. It targets existing shared namespace resources, using their stable `namespaceId` values. It does not create namespaces or change access permissions. Namespace routing is available in Redis Cloud and enabled in self-managed deployments. Configure a policy to use it for a store. For self-managed process settings, see [Configure namespace routing](https://redis.io/docs/latest/operate/iris/agent-memory/self-managed/configuration#enable-namespace-routing).

## Understand placement and admission

| Request | Behavior |
|:--------|:---------|
| Create a memory with `namespaceId` | Use that destination after admission checks. The service never chooses another destination. |
| Create a memory without `namespaceId` | Create it without a namespace. Automatic routing does not place it in a shared namespace. |
| Move a memory with `namespaceId` | Use that destination after admission checks. This applies to a bulk move and to a single `PATCH` of `namespaceId`. |
| Move a memory without `namespaceId` | Evaluate automatic routing only if the memory is already in a shared namespace. |
| Update a memory | Check the merged record against admission at its current destination. An update never chooses another destination. |

An automatic move keeps the current placement and returns a per-item error if routing is disabled, no destination qualifies, or the memory is personal or has no namespace. Inspect both `moved` and `errors` in a move response. Bulk creation also reports failures per record in `errors`. An explicit move to an archived namespace returns `409 Conflict`.

## Configure a policy

These examples continue from the [quickstart](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/quickstart). Load the same `.env` connection values and reuse its user ID. Expand an example to copy a complete program that connects for you.

Each run repeats every write in the program. Session-event writes and memory creates with a new idempotency key add new records, so run cleanup or change the identifiers before rerunning.


The policy routes memories to an existing shared namespace. [Create a shared namespace](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/namespaces#create-a-shared-namespace) for the destination and keep its ID:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
travel_namespace_id = memory.shared_namespace("example-team-travel")
print("Destination:", travel_namespace_id)
```

##### cURL (Iris)

```bash
TRAVEL_NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \
  -X POST "$MEMORY_API/namespaces" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"name": "example-team-travel", "scope": "SHARED"}' | jq -er '.namespace.namespaceId')
echo "Destination: $TRAVEL_NAMESPACE_ID"
```



Read the current policy before changing it:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
current = memory.client.get_namespace_routing().policy
print(current.model_dump_json(by_alias=True, indent=2))
```

##### cURL (Iris)

```bash
curl --fail-with-body --silent --show-error \
  "$MEMORY_API/namespace-routing" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" > current-policy.json
jq .policy current-policy.json
```



This example accepts text containing `travel` into the destination namespace, ignoring case. **PUT replaces the entire policy.** The policy applies to every application that uses the store, so the example keeps the existing rules and replaces only a rule with the same ID. Replacing the policy requires a user credential with an admin role (`suite_admin` or `product_admin`). Agent API keys get `403 Forbidden`, even with full permissions:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
travel_rule = {
    "id": "example-travel",
    "enabled": True,
    "action": "ACCEPT",
    "destination_namespace_id": travel_namespace_id,
    "strategies": [{
        "kind": "MATCHER_SPEC",
        "matcher": {"kind": "REGEX", "regex": {"pattern": "(?i)travel"}},
    }],
}
# PUT replaces the whole policy, so keep the rules that are already there.
kept = [rule for rule in current.rules or [] if rule.id != travel_rule["id"]]
saved = memory.client.put_namespace_routing(
    policy={"enabled": True, "rules": [*kept, travel_rule]},
)
print(saved.policy.model_dump_json(by_alias=True, indent=2))
```

##### cURL (Iris)

```bash
# PUT replaces the whole policy, so keep the rules that are already there.
jq --arg ns "$TRAVEL_NAMESPACE_ID" '{policy: {
  enabled: true,
  rules: ([(.policy.rules // [])[] | select(.id != "example-travel")] + [{
    id: "example-travel",
    enabled: true,
    action: "ACCEPT",
    destinationNamespaceId: $ns,
    strategies: [{kind: "MATCHER_SPEC", matcher: {kind: "REGEX", regex: {pattern: "(?i)travel"}}}]
  }])
}}' current-policy.json > policy.json
curl --fail-with-body --silent --show-error \
  -X PUT "$MEMORY_API/namespace-routing" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' --data-binary @policy.json | jq .policy
```



## How rules are evaluated

In this example, `example-travel` is an accept rule with one strategy, a regex matcher.

An enabled `ACCEPT` rule selects a candidate destination. Accept rules run in list order. Every enabled `REJECT` rule for that destination applies, including rules later in the list. A matching reject vetoes the candidate. Automatic routing then tries another candidate; explicit placement fails rather than choosing another namespace.

Strategies within a rule run in list order; the first match fires the rule. An accept rule with no strategies always matches. A reject rule with no strategies never matches. Use `MATCHER_SPEC` with a `REGEX` matcher for text patterns, or `LLM_PROMPT` with `llmPrompt` for a model-evaluated instruction. Each strategy must contain only the block for its declared `kind`.

A store without a saved policy returns an empty, disabled policy. Regular expressions use RE2 syntax. Lookaround, backreferences, invalid expressions, and patterns that can match empty text are not supported. A policy supports up to 32 rules and 8 strategies per rule. See [`PutNamespaceRouting`](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/api-reference#operation/PutNamespaceRouting) for validation and error responses.

## Move memories with the policy

To evaluate the policy for an existing shared memory, omit the destination from the move request. First create a test memory in another shared namespace:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
source_namespace_id = memory.shared_namespace("example-routing-source")
note = memory.remember(
    "Travel policy allows train tickets.",
    owner=OWNER_ID,
    namespace_id=source_namespace_id,
    memory_id="example-routing-travel",
)
```

##### cURL (Iris)

```bash
SOURCE_NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \
  -X POST "$MEMORY_API/namespaces" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"name": "example-routing-source", "scope": "SHARED"}' | jq -er '.namespace.namespaceId')
jq -n --arg owner "$OWNER_ID" --arg ns "$SOURCE_NAMESPACE_ID" --arg key "$(uuidgen)" '{
  memories: [{
    memoryId: "example-routing-travel",
    ownerId: $owner,
    namespaceId: $ns,
    text: "Travel policy allows train tickets."
  }],
  idempotencyKey: $key
}' > memories.json
curl --fail-with-body --silent --show-error \
  -X POST "$MEMORY_API/memories" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' --data-binary @memories.json | jq .
```



Then move it without a destination:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
moved = memory.client.move_long_term_memories(memory_ids=[note.id])
print("Moved:", moved.moved)
for error in moved.errors or []:
    print("Not moved:", error.memory_id, error.status, error.title, error.detail)
```

##### cURL (Iris)

```bash
curl --fail-with-body --silent --show-error \
  -X POST "$MEMORY_API/memories/move" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"memoryIds": ["example-routing-travel"]}' | jq '{moved, errors}'
```



The example memory must already belong to a shared namespace and contain matching text. If routing is disabled or no rule matches, the memory stays where it is and the response reports a per-item error:

```json
{
  "moved": [],
  "errors": [
    {
      "memoryId": "example-routing-travel",
      "status": 400,
      "title": "Namespace Routing Did Not Match",
      "detail": "namespace routing matched no destination",
      "type": "/errors/invalid-data"
    }
  ]
}
```

The request accepts 1–100 IDs. To choose a destination explicitly, pass `namespaceId`. Explicit placement still runs admission checks:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
moved = memory.client.move_long_term_memories(
    memory_ids=[note.id], namespace_id=travel_namespace_id,
)
print("Moved:", moved.moved)
for error in moved.errors or []:
    print("Not moved:", error.memory_id, error.status, error.title, error.detail)
```

##### cURL (Iris)

```bash
curl --fail-with-body --silent --show-error \
  -X POST "$MEMORY_API/memories/move" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg ns "$TRAVEL_NAMESPACE_ID" '{memoryIds: ["example-routing-travel"], namespaceId: $ns}')" | jq '{moved, errors}'
```



To place a single memory, you can also send `PATCH /iris/data/v1/agent-memory/stores/{storeId}/memories/{memoryId}` with only `namespaceId`. The same admission checks apply, and a namespace-only update keeps the memory's current expiration (TTL). See [Place and retrieve memories](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/namespaces#place-and-retrieve-memories).

