Namespaces

Group session memory and long-term memory with optional namespaces.

Namespaces are optional. Use them to group memories for a project, team, or user. For example, a travel agent can keep a user's trip memories in a personal example-travel namespace.

Create the namespace before writing to it. The response contains a server-generated namespaceId. Save this ID and use it as namespaceId on session events and long-term memory writes. You will also use this ID to search for memories in that namespace. Names and paths can change when you rename a namespace, but its ID stays the same.

Create a personal namespace

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.

This example uses a personal namespace to group the trip memories. It creates a personal namespace for the user's travel memories and keeps the returned ID:

created = memory.client.create_namespace(request={
    "name": "example-travel",
    "scope": models.NamespaceScope.PERSONAL,
    "owner_id": OWNER_ID,
})
namespace_id = created.namespace.namespace_id
print("Personal namespace:", namespace_id)
jq -n --arg owner "$OWNER_ID" \
  '{name: "example-travel", scope: "PERSONAL", ownerId: $owner}' > namespace.json
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-binary @namespace.json | jq -er '.namespace.namespaceId')
echo "Personal namespace: $NAMESPACE_ID"

Save the ID and reuse it on later runs. Creating the same namespace again returns 409 Conflict. If you lose the ID, list personal roots with scope=PERSONAL and ownerId set to the user ID, then find the namespace by name.

Start a new session with this namespace. Extracted memories inherit it:

conversation = memory.conversation(
    owner=OWNER_ID,
    session_id="example-travel-namespace-session",
    namespace_id=namespace_id,
)
conversation.user("I am visiting Tokyo and Kyoto next month.")
jq -n --arg owner "$OWNER_ID" --arg ns "$NAMESPACE_ID" \
  --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg key "$(uuidgen)" '{
  actorId: $owner,
  role: "USER",
  content: [{text: "I am visiting Tokyo and Kyoto next month."}],
  createdAt: $now,
  namespaceId: $ns,
  idempotencyKey: $key
}' > event.json
curl --fail-with-body --silent --show-error \
  -X POST "$MEMORY_API/sessions/example-travel-namespace-session/events" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' --data-binary @event.json | jq .

Create a shared namespace

A shared namespace groups memories that an application uses across users or agents. For example, a travel-planning team can use a shared example-team-travel namespace for information that everyone on the team needs. The namespace has SHARED scope and no namespace owner. Individual long-term memories still have their own ownerId values.

Applications use the same namespace ID when starting sessions or creating long-term memories for the team. Each conversation keeps its own session ID. Memories extracted from those sessions use the shared namespace, so a later search can recall information from several conversations and owners.

Your application decides who can contribute to and retrieve the team's memories. Check the user's access before writing or searching. SHARED scope describes how memories are grouped; it does not grant users permission to access them.

Create the namespace with scope set to SHARED and no ownerId. In Python, memory.shared_namespace() returns the ID of the shared namespace with that name, and creates the namespace only if it does not exist. The cURL request creates the namespace directly:

shared_namespace_id = memory.shared_namespace("example-team-travel")
print("Shared namespace:", shared_namespace_id)
SHARED_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 "Shared namespace: $SHARED_NAMESPACE_ID"

Creating the same root again returns 409 Conflict. If you lose the ID, list root namespaces with scope=SHARED and find example-team-travel, or call memory.shared_namespace() again.

To add a conversation to this namespace, use a new session ID and set its first event's namespace ID to the shared ID. Use memory.conversation(..., namespace_id=shared_namespace_id) in Python, or set "namespaceId" on the first event in cURL. For direct long-term memory creation, set the namespace ID on each record. Creating the namespace does not move existing memories into it.

This example creates one team memory in the shared namespace:

memory.remember(
    "The team books refundable rail passes for every Japan trip.",
    owner=OWNER_ID,
    namespace_id=shared_namespace_id,
)
jq -n --arg owner "$OWNER_ID" --arg ns "$SHARED_NAMESPACE_ID" --arg key "$(uuidgen)" '{
  memories: [{
    memoryId: "example-rail-passes",
    ownerId: $owner,
    namespaceId: $ns,
    text: "The team books refundable rail passes for every Japan trip."
  }],
  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 .

Recall shared memories

After writing memories to the shared namespace, search with its namespace ID. An empty namespace returns no memories.

memory.recall() searches the owner's memories and then each namespace in shared. The shared searches have no owner filter, so results can include memories associated with different owners in that namespace. The CLI --shared option takes the namespace name. The cURL request searches only the shared namespace. Add an ownerId filter when you need only one owner's memories.

known = memory.recall(
    "What travel requirements has the team discussed?",
    owner=OWNER_ID,
    shared=[shared_namespace_id],
)
print(known.as_prompt())
jq -n --arg ns "$SHARED_NAMESPACE_ID" '{
  query: "What travel requirements has the team discussed?",
  filter: {namespaceId: {eq: $ns}},
  limit: 5
}' > search.json
curl --fail-with-body --silent --show-error \
  -X POST "$MEMORY_API/memories/search" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' --data-binary @search.json | jq '.items[] | {memoryId, ownerId, text}'
set -euo pipefail

iris agent-memory memories search \
  "What travel requirements has the team discussed?" \
  --owner quickstart-user --shared example-team-travel --prompt

The namespace filter selects that exact namespace. To search its children too, list them and include their IDs with filter.namespaceId.in.

Place and retrieve memories

Pass "namespaceId": "<namespace-id>" on the first event when you start a session. The first event fixes the session namespace. Later events cannot add or change it. Memories extracted from the session use that namespace, so use a new session ID when adding a namespace to the quickstart example.

For direct long-term memory creation, set namespaceId on each record. To move one existing memory, send PATCH /iris/data/v1/agent-memory/stores/{storeId}/memories/{memoryId} with only namespaceId (memory.client.update_long_term_memory() in Python). A namespace-only update keeps the memory's current expiration (TTL):

hotel = memory.remember(
    "The user is staying at a hotel near Gion in Kyoto.",
    owner=OWNER_ID,
    memory_id="example-kyoto-hotel",
)
updated = memory.client.update_long_term_memory(
    memory_id=hotel.id, namespace_id=namespace_id,
)
print(updated.memory_id, "is in", updated.namespace_id)
jq -n --arg owner "$OWNER_ID" --arg key "$(uuidgen)" '{
  memories: [{memoryId: "example-kyoto-hotel", ownerId: $owner, text: "The user is staying at a hotel near Gion in Kyoto."}],
  idempotencyKey: $key
}' > hotel.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 @hotel.json > /dev/null
curl --fail-with-body --silent --show-error \
  -X PATCH "$MEMORY_API/memories/example-kyoto-hotel" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg ns "$NAMESPACE_ID" '{namespaceId: $ns}')" | jq '{memoryId, namespaceId}'

To move several memories in one request, use POST /iris/data/v1/agent-memory/stores/{storeId}/memories/move (memory.client.move_long_term_memories() in Python) with their IDs and the destination namespaceId. Both requests run admission checks at the destination. Check both moved and errors in a bulk move response before treating the batch as complete:

arrival = memory.remember(
    "The user lands in Tokyo on a Friday evening.",
    owner=OWNER_ID,
    memory_id="example-tokyo-arrival",
)
moved = memory.client.move_long_term_memories(
    memory_ids=[arrival.id], namespace_id=namespace_id,
)
print("Moved:", moved.moved)
for error in moved.errors or []:
    print("Not moved:", error.memory_id, error.status, error.title)
jq -n --arg owner "$OWNER_ID" --arg key "$(uuidgen)" '{
  memories: [{memoryId: "example-tokyo-arrival", ownerId: $owner, text: "The user lands in Tokyo on a Friday evening."}],
  idempotencyKey: $key
}' > arrival.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 @arrival.json > /dev/null
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 "$NAMESPACE_ID" '{memoryIds: ["example-tokyo-arrival"], namespaceId: $ns}')" | jq '{moved, errors}'

Search with filter.namespaceId.eq for one namespace or filter.namespaceId.in for several IDs. These filters match exact namespace IDs; they do not expand a parent into its descendants. List the children and include their IDs when you need to search several levels of a hierarchy. Keep the ownerId filter when recalling one user's memories:

Your application must check that the current user can access each namespace before querying it. In on-prem deployments with ownership enforcement enabled, personal namespace access follows the root owner's authenticated user ID. Shared namespaces are accessible to callers with the required store permission. If a shared namespace is only for one team, enforce that rule in your application or use separate stores and grants. See Authorize every memory request.

Create a namespace hierarchy

Choose a root scope when you create a namespace:

Scope Creation fields Example use
PERSONAL name, scope, and ownerId A user's travel plans.
SHARED name and scope, without ownerId Information used across a team.

Create a child with name and parentId. You can also create a hierarchy with path, such as example-travel/kyoto, and the root scope. Supply ownerId for a personal path. Missing path segments are created as needed. Do not combine path with name or parentId.

This example creates a child of the personal namespace:

child = memory.client.create_namespace(request={
    "name": "japan",
    "parent_id": namespace_id,
})
print(child.namespace.path, child.namespace.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 "$(jq -n --arg parent "$NAMESPACE_ID" '{name: "japan", parentId: $parent}')" | jq '.namespace | {path, namespaceId}'

This example creates a personal path. It reuses the existing example-travel root and creates the missing kyoto segment:

leaf = memory.client.create_namespace(request={
    "path": "example-travel/kyoto",
    "scope": models.NamespaceScope.PERSONAL,
    "owner_id": OWNER_ID,
})
print(leaf.namespace.path, leaf.namespace.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 "$(jq -n --arg owner "$OWNER_ID" '{path: "example-travel/kyoto", scope: "PERSONAL", ownerId: $owner}')" | jq '.namespace | {path, namespaceId}'

Names and paths are case-sensitive. The path does not encode scope. Use the returned ID to distinguish namespaces that have the same path under different owners or scopes.

List direct children with parentId:

children = memory.client.list_namespaces(parent_id=namespace_id)
for item in children.items:
    print(item.path, item.namespace_id, item.state)
curl --fail-with-body --silent --show-error --get \
  "$MEMORY_API/namespaces" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  --data-urlencode "parentId=$NAMESPACE_ID" | jq '.items[] | {path, namespaceId, state}'

Manage namespaces

Operation Behavior
List List roots, or pass parentId to list direct children. Sort by id, name, createdAt, or updatedAt with sortBy; sortOrder accepts ASC or DESC. Defaults are id and ASC. Follow nextCursor with the same filters and sort options.
Get Retrieve the current name, path, scope, and state by ID.
Rename Update name; continue using the same ID for placement and retrieval.
Archive Set state to ARCHIVED to stop new children and placements. You cannot unarchive a namespace.
Delete Delete an empty leaf namespace. A namespace with children or memory placements returns 409 Conflict.

Creating a namespace at an occupied location returns 409 Conflict, including when the existing namespace is archived. Save and reuse the returned ID instead of creating the same namespace on each agent turn. For request fields and error responses, see the namespace API reference.

Sort namespaces

This example lists shared roots by name:

namespaces = memory.client.list_namespaces(
    scope=models.NamespaceScope.SHARED,
    sort_by=models.NamespaceSortBy.NAME,
    sort_order=models.NamespaceSortOrder.ASC,
    limit=20,
)
for item in namespaces.items:
    print(item.name, item.namespace_id)
print("Next cursor:", namespaces.next_cursor)
curl --fail-with-body --silent --show-error --get \
  "$MEMORY_API/namespaces" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  --data-urlencode 'scope=SHARED' --data-urlencode 'limit=20' \
  --data-urlencode 'sortBy=name' --data-urlencode 'sortOrder=ASC' | jq '{names: [.items[].name], nextCursor}'

Name sorting uses the display name with Redis text collation, rather than the hierarchy path. Equal sort values use namespace ID ascending as a tie-breaker.

Route and admit shared memories

An explicit namespace destination must pass its admission rules. Direct creation without a namespace stays unassigned. To route an existing shared memory, omit namespaceId from a move request. Personal and unassigned memories are not candidates for automatic routing. See Namespace routing for policy setup, examples, and per-item errors.

Delete the example namespaces

Only an empty leaf namespace can be deleted. This example deletes the example user's session data and long-term memories, then deletes each example namespace after its children. Wait until extraction for the example session finishes first; a namespace that still has memory placements returns 409 Conflict.

forgotten = memory.forget(OWNER_ID)
print(f"Deleted {forgotten.sessions} sessions and {forgotten.memories} memories")


def delete_tree(namespace_id):
    # Only an empty leaf can be deleted, so remove children first.
    for child in memory.client.list_namespaces(parent_id=namespace_id).items:
        delete_tree(child.namespace_id)
    memory.client.delete_namespace(namespace_id=namespace_id)


personal = memory.client.list_namespaces(
    scope=models.NamespaceScope.PERSONAL, owner_id=OWNER_ID,
)
shared, cursor = [], None
while True:
    page = memory.client.list_namespaces(
        scope=models.NamespaceScope.SHARED, limit=200, cursor=cursor,
    )
    shared += page.items
    cursor = page.next_cursor
    if not cursor:
        break

for root in [*personal.items, *shared]:
    if root.name in ("example-travel", "example-team-travel"):
        delete_tree(root.namespace_id)
        print("Deleted", root.path)
AUTH="Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY"

curl --fail-with-body --silent --show-error -X DELETE \
  "$MEMORY_API/sessions/example-travel-namespace-session" -H "$AUTH"

jq -n --arg owner "$OWNER_ID" '{filter: {ownerId: {eq: $owner}}, limit: 100}' > owned.json
IDS=$(curl --fail-with-body --silent --show-error -X POST "$MEMORY_API/memories/search" \
  -H "$AUTH" -H 'Content-Type: application/json' --data-binary @owned.json | jq -c '[.items[].memoryId]')
if [ "$IDS" != "[]" ]; then
  curl --fail-with-body --silent --show-error -X POST "$MEMORY_API/memories/bulk-delete" \
    -H "$AUTH" -H 'Content-Type: application/json' \
    --data "$(jq -n --argjson ids "$IDS" '{memoryIds: $ids}')" | jq .
fi

delete_tree() {  # only an empty leaf can be deleted, so remove children first
  local child
  for child in $(curl --fail-with-body --silent --show-error --get "$MEMORY_API/namespaces" \
      -H "$AUTH" --data-urlencode "parentId=$1" | jq -r '.items[].namespaceId'); do
    delete_tree "$child"
  done
  curl --fail-with-body --silent --show-error -X DELETE "$MEMORY_API/namespaces/$1" -H "$AUTH"
  echo "Deleted $1"
}

PERSONAL_ID=$(curl --fail-with-body --silent --show-error --get "$MEMORY_API/namespaces" -H "$AUTH" \
  --data-urlencode 'scope=PERSONAL' --data-urlencode "ownerId=$OWNER_ID" \
  | jq -r '.items[] | select(.name == "example-travel") | .namespaceId')
SHARED_ID=$(curl --fail-with-body --silent --show-error --get "$MEMORY_API/namespaces" -H "$AUTH" \
  --data-urlencode 'scope=SHARED' --data-urlencode 'limit=200' \
  | jq -r '.items[] | select(.name == "example-team-travel") | .namespaceId')
for id in $PERSONAL_ID $SHARED_ID; do
  delete_tree "$id"
done
RATE THIS PAGE
Back to top ↑