Namespace routing

Configure shared namespace routing and admission rules for long-term memories.

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.

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. 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 for the destination and keep its ID:

travel_namespace_id = memory.shared_namespace("example-team-travel")
print("Destination:", travel_namespace_id)
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:

current = memory.client.get_namespace_routing().policy
print(current.model_dump_json(by_alias=True, indent=2))
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:

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

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",
)
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:

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

{
  "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:

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

RATE THIS PAGE
Back to top ↑