{
  "schema_version": 2,
  "id": "develop/ai/context-engine/agent-memory/namespaces/namespace-routing",
  "title": "Namespace routing",
  "url": "https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/namespaces/namespace-routing/",
  "summary": "Configure shared namespace routing and admission rules for long-term memories.",
  "aliases": [
    "/develop/ai/context-engine/agent-memory/namespace-routing/"
  ],
  "tags": [
    "docs",
    "develop",
    "ai"
  ],
  "last_updated": "2026-11-03T10:02:56-08:00",
  "page_type": "content",
  "content_hash": "e34d43e87a28f47188b79c23c49f0b6dcbb787bfff33619309152f983b0e4841",
  "sections": [
    {
      "id": "code-examples",
      "title": "Code examples",
      "role": "example",
      "text": "These examples connect to Iris services using SDKs or HTTP requests with curl. Follow one language throughout each walkthrough.\n\n\nNamespace 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.\n\nThe 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)."
    },
    {
      "id": "understand-placement-and-admission",
      "title": "Understand placement and admission",
      "role": "content",
      "text": "| Request | Behavior |\n|:--------|:---------|\n| Create a memory with `namespaceId` | Use that destination after admission checks. The service never chooses another destination. |\n| Create a memory without `namespaceId` | Create it without a namespace. Automatic routing does not place it in a shared namespace. |\n| Move a memory with `namespaceId` | Use that destination after admission checks. This applies to a bulk move and to a single `PATCH` of `namespaceId`. |\n| Move a memory without `namespaceId` | Evaluate automatic routing only if the memory is already in a shared namespace. |\n| Update a memory | Check the merged record against admission at its current destination. An update never chooses another destination. |\n\nAn 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`."
    },
    {
      "id": "configure-a-policy",
      "title": "Configure a policy",
      "role": "configuration",
      "text": "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.\n\nEach 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.\n\n\nThe 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:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n\n\nRead the current policy before changing it:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n\n\nThis 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:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]"
    },
    {
      "id": "how-rules-are-evaluated",
      "title": "How rules are evaluated",
      "role": "content",
      "text": "In this example, `example-travel` is an accept rule with one strategy, a regex matcher.\n\nAn 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.\n\nStrategies 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`.\n\nA 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."
    },
    {
      "id": "move-memories-with-the-policy",
      "title": "Move memories with the policy",
      "role": "content",
      "text": "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:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n\n\nThen move it without a destination:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n\n\nThe 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:\n\n[code example]\n\nThe request accepts 1–100 IDs. To choose a destination explicitly, pass `namespaceId`. Explicit placement still runs admission checks:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n\n\nTo 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)."
    }
  ],
  "examples": [
    {
      "id": "configure-a-policy-ex0",
      "language": "python",
      "code": "travel_namespace_id = memory.shared_namespace(\"example-team-travel\")\nprint(\"Destination:\", travel_namespace_id)",
      "section_id": "configure-a-policy"
    },
    {
      "id": "configure-a-policy-ex1",
      "language": "bash",
      "code": "TRAVEL_NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \\\n  -X POST \"$MEMORY_API/namespaces\" \\\n  -H \"Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  --data '{\"name\": \"example-team-travel\", \"scope\": \"SHARED\"}' | jq -er '.namespace.namespaceId')\necho \"Destination: $TRAVEL_NAMESPACE_ID\"",
      "section_id": "configure-a-policy"
    },
    {
      "id": "configure-a-policy-ex2",
      "language": "python",
      "code": "current = memory.client.get_namespace_routing().policy\nprint(current.model_dump_json(by_alias=True, indent=2))",
      "section_id": "configure-a-policy"
    },
    {
      "id": "configure-a-policy-ex3",
      "language": "bash",
      "code": "curl --fail-with-body --silent --show-error \\\n  \"$MEMORY_API/namespace-routing\" \\\n  -H \"Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY\" > current-policy.json\njq .policy current-policy.json",
      "section_id": "configure-a-policy"
    },
    {
      "id": "configure-a-policy-ex4",
      "language": "python",
      "code": "travel_rule = {\n    \"id\": \"example-travel\",\n    \"enabled\": True,\n    \"action\": \"ACCEPT\",\n    \"destination_namespace_id\": travel_namespace_id,\n    \"strategies\": [{\n        \"kind\": \"MATCHER_SPEC\",\n        \"matcher\": {\"kind\": \"REGEX\", \"regex\": {\"pattern\": \"(?i)travel\"}},\n    }],\n}\n# PUT replaces the whole policy, so keep the rules that are already there.\nkept = [rule for rule in current.rules or [] if rule.id != travel_rule[\"id\"]]\nsaved = memory.client.put_namespace_routing(\n    policy={\"enabled\": True, \"rules\": [*kept, travel_rule]},\n)\nprint(saved.policy.model_dump_json(by_alias=True, indent=2))",
      "section_id": "configure-a-policy"
    },
    {
      "id": "configure-a-policy-ex5",
      "language": "bash",
      "code": "# PUT replaces the whole policy, so keep the rules that are already there.\njq --arg ns \"$TRAVEL_NAMESPACE_ID\" '{policy: {\n  enabled: true,\n  rules: ([(.policy.rules // [])[] | select(.id != \"example-travel\")] + [{\n    id: \"example-travel\",\n    enabled: true,\n    action: \"ACCEPT\",\n    destinationNamespaceId: $ns,\n    strategies: [{kind: \"MATCHER_SPEC\", matcher: {kind: \"REGEX\", regex: {pattern: \"(?i)travel\"}}}]\n  }])\n}}' current-policy.json > policy.json\ncurl --fail-with-body --silent --show-error \\\n  -X PUT \"$MEMORY_API/namespace-routing\" \\\n  -H \"Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY\" \\\n  -H 'Content-Type: application/json' --data-binary @policy.json | jq .policy",
      "section_id": "configure-a-policy"
    },
    {
      "id": "move-memories-with-the-policy-ex0",
      "language": "python",
      "code": "source_namespace_id = memory.shared_namespace(\"example-routing-source\")\nnote = memory.remember(\n    \"Travel policy allows train tickets.\",\n    owner=OWNER_ID,\n    namespace_id=source_namespace_id,\n    memory_id=\"example-routing-travel\",\n)",
      "section_id": "move-memories-with-the-policy"
    },
    {
      "id": "move-memories-with-the-policy-ex1",
      "language": "bash",
      "code": "SOURCE_NAMESPACE_ID=$(curl --fail-with-body --silent --show-error \\\n  -X POST \"$MEMORY_API/namespaces\" \\\n  -H \"Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  --data '{\"name\": \"example-routing-source\", \"scope\": \"SHARED\"}' | jq -er '.namespace.namespaceId')\njq -n --arg owner \"$OWNER_ID\" --arg ns \"$SOURCE_NAMESPACE_ID\" --arg key \"$(uuidgen)\" '{\n  memories: [{\n    memoryId: \"example-routing-travel\",\n    ownerId: $owner,\n    namespaceId: $ns,\n    text: \"Travel policy allows train tickets.\"\n  }],\n  idempotencyKey: $key\n}' > memories.json\ncurl --fail-with-body --silent --show-error \\\n  -X POST \"$MEMORY_API/memories\" \\\n  -H \"Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY\" \\\n  -H 'Content-Type: application/json' --data-binary @memories.json | jq .",
      "section_id": "move-memories-with-the-policy"
    },
    {
      "id": "move-memories-with-the-policy-ex2",
      "language": "python",
      "code": "moved = memory.client.move_long_term_memories(memory_ids=[note.id])\nprint(\"Moved:\", moved.moved)\nfor error in moved.errors or []:\n    print(\"Not moved:\", error.memory_id, error.status, error.title, error.detail)",
      "section_id": "move-memories-with-the-policy"
    },
    {
      "id": "move-memories-with-the-policy-ex3",
      "language": "bash",
      "code": "curl --fail-with-body --silent --show-error \\\n  -X POST \"$MEMORY_API/memories/move\" \\\n  -H \"Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  --data '{\"memoryIds\": [\"example-routing-travel\"]}' | jq '{moved, errors}'",
      "section_id": "move-memories-with-the-policy"
    },
    {
      "id": "move-memories-with-the-policy-ex4",
      "language": "json",
      "code": "{\n  \"moved\": [],\n  \"errors\": [\n    {\n      \"memoryId\": \"example-routing-travel\",\n      \"status\": 400,\n      \"title\": \"Namespace Routing Did Not Match\",\n      \"detail\": \"namespace routing matched no destination\",\n      \"type\": \"/errors/invalid-data\"\n    }\n  ]\n}",
      "section_id": "move-memories-with-the-policy"
    },
    {
      "id": "move-memories-with-the-policy-ex5",
      "language": "python",
      "code": "moved = memory.client.move_long_term_memories(\n    memory_ids=[note.id], namespace_id=travel_namespace_id,\n)\nprint(\"Moved:\", moved.moved)\nfor error in moved.errors or []:\n    print(\"Not moved:\", error.memory_id, error.status, error.title, error.detail)",
      "section_id": "move-memories-with-the-policy"
    },
    {
      "id": "move-memories-with-the-policy-ex6",
      "language": "bash",
      "code": "curl --fail-with-body --silent --show-error \\\n  -X POST \"$MEMORY_API/memories/move\" \\\n  -H \"Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  --data \"$(jq -n --arg ns \"$TRAVEL_NAMESPACE_ID\" '{memoryIds: [\"example-routing-travel\"], namespaceId: $ns}')\" | jq '{moved, errors}'",
      "section_id": "move-memories-with-the-policy"
    }
  ]
}
