Long-term memory
Create and search memories that remain useful across conversations.
Long-term memory stores information that remains useful beyond one conversation. Use automatic extraction or direct creation to store memories, then search for information relevant to an agent turn.
Create long-term memories
Redis Agent Memory provides two ways to create memories:
- Automatic extraction: Redis Agent Memory processes session events asynchronously and creates durable memories from relevant information. Configure the extraction cadence to control how often session events are processed.
- Direct creation: Your application creates one or more long-term memories through the application programming interface (API) or a software development kit (SDK). Use direct creation when importing existing information or when your application determines exactly what to store.
Configure the long-term-memory time to live (TTL) separately from the session-memory TTL.
See memory configuration to configure the extraction cadence and long-term-memory TTL in Redis Cloud.
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 creates three memories for the quickstart user. Direct creation does not wait for background extraction, so the memories are searchable immediately:
seeded = [
memory.remember("The user is vegetarian.", owner=OWNER_ID, topics=["food"]),
memory.remember("The user prefers spicy food.", owner=OWNER_ID, topics=["food"]),
memory.remember("The user prefers a window seat.", owner=OWNER_ID, topics=["travel"]),
]
for created in seeded:
print("Stored", created.id, created.text)
from redis_iris.agent_memory import AgentMemory
OWNER_ID = "quickstart-user"
memory = AgentMemory.from_env()
seeded = [
memory.remember("The user is vegetarian.", owner=OWNER_ID, topics=["food"]),
memory.remember("The user prefers spicy food.", owner=OWNER_ID, topics=["food"]),
memory.remember("The user prefers a window seat.", owner=OWNER_ID, topics=["travel"]),
]
for created in seeded:
print("Stored", created.id, created.text)
known = memory.recall(
"What dietary requirements and food preferences does the user have?",
owner=OWNER_ID,
limit=5,
)
print(known.as_prompt())
recent = memory.client.search_long_term_memory(request={
"filter_": {"owner_id": {"eq": OWNER_ID}},
"sort_by": "updatedAt",
"sort_order": "DESC",
"limit": 10,
})
for record in recent.items:
print(record.updated_at, record.memory_id, record.text)
memory.delete(created.id for created in seeded)
print("Deleted", len(seeded), "memories")
jq -n --arg owner "$OWNER_ID" --arg key "$(uuidgen)" '{
idempotencyKey: $key,
memories: [
{memoryId: "example-diet", text: "The user is vegetarian.", ownerId: $owner, topics: ["food"]},
{memoryId: "example-cuisine", text: "The user prefers spicy food.", ownerId: $owner, topics: ["food"]},
{memoryId: "example-seat", text: "The user prefers a window seat.", ownerId: $owner, topics: ["travel"]}
]
}' > memories.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @memories.json | jq .
set -euo pipefail
MEMORY_API="${IRIS_AGENT_MEMORY_URL%/}/iris/data/v1/agent-memory/stores/$IRIS_AGENT_MEMORY_STORE_ID"
AUTH="Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY"
OWNER_ID=quickstart-user
jq -n --arg owner "$OWNER_ID" --arg key "$(uuidgen)" '{
idempotencyKey: $key,
memories: [
{memoryId: "example-diet", text: "The user is vegetarian.", ownerId: $owner, topics: ["food"]},
{memoryId: "example-cuisine", text: "The user prefers spicy food.", ownerId: $owner, topics: ["food"]},
{memoryId: "example-seat", text: "The user prefers a window seat.", ownerId: $owner, topics: ["travel"]}
]
}' > memories.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @memories.json | jq .
jq -n --arg owner "$OWNER_ID" '{
query: "What dietary requirements and food preferences does the user have?",
filter: {ownerId: {eq: $owner}},
limit: 5
}' > search.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories/search" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @search.json \
| jq '.items[] | {memoryId, text, memoryType}'
jq -n --arg owner "$OWNER_ID" '{
filter: {ownerId: {eq: $owner}},
sortBy: "updatedAt",
sortOrder: "DESC",
limit: 10
}' > recent.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories/search" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @recent.json \
| jq '.items[] | {updatedAt, memoryId, text}'
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories/bulk-delete" \
-H "$AUTH" -H 'Content-Type: application/json' \
--data '{"memoryIds": ["example-diet", "example-cuisine", "example-seat"]}' | jq .
In Python, remember derives a stable memory ID from the owner and text; the cURL example sets each memoryId explicitly. Either way, running the example again does not duplicate the memories. A memoryId that already exists is rejected for that record and reported in the response's errors array, and the stored memory is left unchanged. To change a memory, see Update a memory.
Reuse an idempotencyKey with the same request to retry a bulk create safely. Redis Agent Memory replays the saved result during the retry window, which lasts at most 24 hours and no longer than the store's memory TTL. Reusing a key with a different request returns 409 Conflict.
Memory fields
When you create a memory directly, you supply memoryId, text, and ownerId. The Python SDK's remember method derives memoryId when you omit it. The server sets createdAt, updatedAt, and revision. During automatic extraction, the server creates the memory ID and content and copies the session owner into ownerId. Use that same stable user ID when querying personal memories; see Authorize every memory request.
A long-term memory record includes:
| Field | Purpose |
|---|---|
memoryId |
Unique identifier for the memory. |
text |
Memory content used for retrieval. |
memoryType |
Built-in or custom memory type. Defaults to semantic. |
sessionId |
Session associated with the memory. |
ownerId |
User or entity associated with the memory. |
namespaceId |
Optional stable namespace ID. |
attributes |
Custom memory fields and their values. |
topics |
Topic tags used to categorize the memory. |
revision |
Opaque token that changes on every write. Use it for conditional updates. |
documentManaged |
true when MemoryETL created the memory. |
createdAt |
Time the memory was created. |
updatedAt |
Time the memory was last updated. |
Search long-term memory
Search by meaning and use ownerId to restrict recall to the relevant user or entity. Add filters for a session, namespace, topic, or memory type as needed.
For request fields, filter operators, and response schemas, see SearchLongTermMemory.
For domain-specific fields and extraction instructions, see Custom memory types. To group memories in a hierarchy, see Namespaces.
For controls that apply during automatic extraction, see Exclude sensitive data from automatic extraction.
Search by meaning
This example finds memories about the user's food preferences. recall restricts the search to the owner's memories:
known = memory.recall(
"What dietary requirements and food preferences does the user have?",
owner=OWNER_ID,
limit=5,
)
print(known.as_prompt())
jq -n --arg owner "$OWNER_ID" '{
query: "What dietary requirements and food preferences does the user have?",
filter: {ownerId: {eq: $owner}},
limit: 5
}' > search.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories/search" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @search.json \
| jq '.items[] | {memoryId, text, memoryType}'
set -euo pipefail
iris agent-memory memories search \
"What dietary requirements and food preferences does the user have?" \
--owner quickstart-user --limit 5
set -euo pipefail
iris agent-memory memories search \
"What dietary requirements and food preferences does the user have?" \
--owner quickstart-user --limit 5
Filter and sort without semantic search
Omit query to retrieve records by filters without a semantic query. For example, retrieve the user's most recently updated memories:
recent = memory.client.search_long_term_memory(request={
"filter_": {"owner_id": {"eq": OWNER_ID}},
"sort_by": "updatedAt",
"sort_order": "DESC",
"limit": 10,
})
for record in recent.items:
print(record.updated_at, record.memory_id, record.text)
jq -n --arg owner "$OWNER_ID" '{
filter: {ownerId: {eq: $owner}},
sortBy: "updatedAt",
sortOrder: "DESC",
limit: 10
}' > recent.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories/search" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @recent.json \
| jq '.items[] | {updatedAt, memoryId, text}'
sortBy accepts createdAt or updatedAt only when query is absent. sortOrder accepts ASC or DESC, defaults to descending, and requires sortBy. Semantic searches remain ranked by similarity and cannot use these sort fields. limit accepts 1–100 and defaults to 50. Pass nextCursor as cursor with the same query to continue.
Filter conditions use filter.op: "ALL" (AND) by default; "ANY" uses OR. To restrict a query to a user, keep the owner condition mandatory. Do not use "ANY" with separate owner and namespace conditions when both must match. These retrieval filters do not replace application access checks.
Set filter.unnamespaced: true to match records with neither the older namespace string label nor a namespace resource. It cannot be combined with namespaceId, and remains mandatory even with op: "ANY".
MemoryETL outputs are included in search by default. Set top-level excludeDocumentManaged: true to omit them. Returned documentManaged records are protected from generic writes: memory creation, PUT, and PATCH requests cannot change their content. The one exception is a PATCH that changes only namespaceId, which moves the record and keeps its TTL. Use the document-ingestion workflow to manage their content.
For custom fields, use filter.attributes, keyed by the field name, with the operators described in AttributeFilterClause. Combine it with memoryType to select the registered custom type.
For example, this search request combines the owner, type, and dietary requirement with op: "ALL". A list clause requires every listed value to be present:
{
"filter": {
"op": "ALL",
"ownerId": {"eq": "<user-id>"},
"memoryType": {"eq": "trip_preference"},
"attributes": {"dietary_requirements": {"list": ["vegetarian"]}}
},
"limit": 10
}
Use exactly one typed clause per attribute: string, number, boolean, or list, matching the registered field type.
Update a memory
Redis Agent Memory provides two ways to change a memory you manage:
- Partial update:
PATCH /memories/{memoryId}changes only the fields you send:text,memoryType,topics,namespaceId,ownerId, orsessionId. It does not changeattributes. An update that changes onlynamespaceIdkeeps the memory's current expiration; any other update restarts the long-term-memory TTL. - Replace or create:
PUT /memories/{memoryId}creates the memory if it does not exist, or replaces it completely. It requirestextandownerId. Omitted optional fields, includingattributes, are cleared, andmemoryTypedefaults tosemantic. Replacement keepscreatedAtand restarts the TTL. The response'screatedfield reports whether the memory was new.
To change a custom memory's attributes, replace the record with PUT.
The example program first creates a memory that records a seat preference. Read the memory before you change it. The HTTP response's revision identifies the version you read:
current = memory.client.get_long_term_memory(memory_id=MEMORY_ID)
print(current.text, current.topics, current.updated_at)
from redis_iris.agent_memory import AgentMemory
OWNER_ID = "quickstart-user"
MEMORY_ID = "example-seat-preference"
memory = AgentMemory.from_env()
memory.client.bulk_create_long_term_memories(memories=[{
"memory_id": MEMORY_ID,
"text": "The user prefers a window seat.",
"owner_id": OWNER_ID,
"topics": ["travel"],
}])
current = memory.client.get_long_term_memory(memory_id=MEMORY_ID)
print(current.text, current.topics, current.updated_at)
updated = memory.client.update_long_term_memory(
memory_id=MEMORY_ID,
text="The user prefers an aisle seat on long flights.",
topics=["travel", "flights"],
)
print(updated.text, updated.topics, updated.updated_at)
memory.delete([MEMORY_ID])
curl --fail-with-body --silent --show-error \
"$MEMORY_API/memories/$MEMORY_ID" -H "$AUTH" > current.json
jq '{text, topics, revision}' current.json
REVISION=$(jq -r .revision current.json)
set -euo pipefail
MEMORY_API="${IRIS_AGENT_MEMORY_URL%/}/iris/data/v1/agent-memory/stores/$IRIS_AGENT_MEMORY_STORE_ID"
AUTH="Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY"
OWNER_ID=quickstart-user
MEMORY_ID=example-seat-preference
jq -n --arg owner "$OWNER_ID" --arg id "$MEMORY_ID" '{
memories: [{memoryId: $id, text: "The user prefers a window seat.", ownerId: $owner, topics: ["travel"]}]
}' > seed.json
curl --fail-with-body --silent --show-error -X POST "$MEMORY_API/memories" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @seed.json > /dev/null
curl --fail-with-body --silent --show-error \
"$MEMORY_API/memories/$MEMORY_ID" -H "$AUTH" > current.json
jq '{text, topics, revision}' current.json
REVISION=$(jq -r .revision current.json)
jq -n --arg revision "$REVISION" --arg key "$(uuidgen)" '{
text: "The user prefers an aisle seat on long flights.",
topics: ["travel", "flights"],
expectedRevision: $revision,
idempotencyKey: $key
}' > patch.json
curl --fail-with-body --silent --show-error \
-X PATCH "$MEMORY_API/memories/$MEMORY_ID" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @patch.json > updated.json
jq '{text, topics, revision}' updated.json
jq -n --arg owner "$OWNER_ID" --arg revision "$(jq -r .revision updated.json)" --arg key "$(uuidgen)" '{
text: "The user prefers an aisle seat and travels with a carry-on bag only.",
ownerId: $owner,
topics: ["travel", "flights"],
expectedRevision: $revision,
idempotencyKey: $key
}' > replace.json
curl --fail-with-body --silent --show-error \
-X PUT "$MEMORY_API/memories/$MEMORY_ID" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @replace.json | jq .
jq -n --arg owner "$OWNER_ID" --arg revision "$REVISION" '{
text: "The user prefers a window seat.",
ownerId: $owner,
expectedRevision: $revision
}' > stale.json
STATUS=$(curl --silent --show-error -o conflict.json -w '%{http_code}' \
-X PUT "$MEMORY_API/memories/$MEMORY_ID" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @stale.json)
echo "HTTP $STATUS"
jq '{status, type, detail}' conflict.json
curl --fail-with-body --silent --show-error -X POST "$MEMORY_API/memories/bulk-delete" \
-H "$AUTH" -H 'Content-Type: application/json' \
--data "$(jq -n --arg id "$MEMORY_ID" '{memoryIds: [$id]}')" > /dev/null
Change the text and topics with a partial update:
updated = memory.client.update_long_term_memory(
memory_id=MEMORY_ID,
text="The user prefers an aisle seat on long flights.",
topics=["travel", "flights"],
)
print(updated.text, updated.topics, updated.updated_at)
jq -n --arg revision "$REVISION" --arg key "$(uuidgen)" '{
text: "The user prefers an aisle seat on long flights.",
topics: ["travel", "flights"],
expectedRevision: $revision,
idempotencyKey: $key
}' > patch.json
curl --fail-with-body --silent --show-error \
-X PATCH "$MEMORY_API/memories/$MEMORY_ID" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @patch.json > updated.json
jq '{text, topics, revision}' updated.json
Replace the whole memory with PUT:
jq -n --arg owner "$OWNER_ID" --arg revision "$(jq -r .revision updated.json)" --arg key "$(uuidgen)" '{
text: "The user prefers an aisle seat and travels with a carry-on bag only.",
ownerId: $owner,
topics: ["travel", "flights"],
expectedRevision: $revision,
idempotencyKey: $key
}' > replace.json
curl --fail-with-body --silent --show-error \
-X PUT "$MEMORY_API/memories/$MEMORY_ID" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @replace.json | jq .
Prevent lost updates
Send expectedRevision with the revision you read to apply a PATCH or PUT only if nobody changed the memory since. A stale revision returns 412 Precondition Failed with the type /errors/revision-conflict. Read the memory again, reapply your change, and retry. Omit expectedRevision for an unconditional write. Search results from older records can omit revision; read the memory by ID before a conditional update.
This request reuses the revision read before the partial update, so it fails:
jq -n --arg owner "$OWNER_ID" --arg revision "$REVISION" '{
text: "The user prefers a window seat.",
ownerId: $owner,
expectedRevision: $revision
}' > stale.json
STATUS=$(curl --silent --show-error -o conflict.json -w '%{http_code}' \
-X PUT "$MEMORY_API/memories/$MEMORY_ID" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @stale.json)
echo "HTTP $STATUS"
jq '{status, type, detail}' conflict.json
PATCH and PUT also accept an idempotencyKey. Reuse the key with the same request to retry safely; Redis Agent Memory replays the saved result during the retry window, which lasts at most 24 hours and no longer than the store's memory TTL. Reusing a key with a different request returns 409 Conflict.
The Python SDK does not yet expose revision, expectedRevision, update idempotency keys, or the PUT operation. Use the HTTP API for replacement and conditional writes.
Delete memories
Delete memories by ID. This example deletes the memories created in Create long-term memories:
memory.delete(created.id for created in seeded)
print("Deleted", len(seeded), "memories")
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories/bulk-delete" \
-H "$AUTH" -H 'Content-Type: application/json' \
--data '{"memoryIds": ["example-diet", "example-cuisine", "example-seat"]}' | jq .
To delete every session and memory that a user owns, see Clean up in the quickstart.
Custom memory types
Custom memory types capture structured information specific to your business domain.
Define a memory type
Configure the type on your Redis Agent Memory service with:
| Setting | Purpose |
|---|---|
| Name | Unique name used as the memory's memoryType. |
| Description | Description of the information represented by the type. |
| Extraction prompt | Instructions that tell Redis Agent Memory when and how to extract the custom memory from session events. |
| Enabled | Controls whether Redis Agent Memory extracts new memories for the type. |
| Custom fields | Structured fields added to memories of this type. |
Custom fields support str, int, float, bool, list[str], list[float], and object. Each field has a name and description that explain what it captures. Every custom memory also includes the built-in long-term memory fields.
For example, a travel application can define a trip_preference type with these fields:
| Field | Type | Captures |
|---|---|---|
destinations |
list[str] |
Cities or countries the user plans to visit. |
travel_period |
str |
Dates or period of the trip. |
dietary_requirements |
list[str] |
Dietary requirements that affect recommendations. |
food_preferences |
list[str] |
Cuisines, flavors, or dining preferences. |
When this type is enabled, Redis Agent Memory can extract a structured trip_preference memory from relevant session events. Each enabled custom type processes session events independently.
Extract custom memories
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.
In the Redis Cloud console, open your service’s Configuration tab, select Edit, and under Memory types & extraction select Add type. Configure and enable trip_preference with the fields in Define a memory type. Use this extraction prompt:
Extract trip requirements only when the user states a destination or travel plan. Preserve explicit dietary requirements and food preferences.
The examples in this section require the registered type. Until it exists, requests that create a trip_preference memory return 400 Bad Request with the message memory type "trip_preference" is not registered on this store.
After enabling the type, add a travel message to a new session:
conversation = memory.conversation(owner=OWNER_ID, session_id="example-trip-preference-session")
saved = conversation.user("I am visiting Tokyo and Kyoto next month. I am vegetarian.")
print("Saved event", saved.event_id)
import time
from redis_iris.agent_memory import AgentMemory
OWNER_ID = "quickstart-user"
memory = AgentMemory.from_env()
conversation = memory.conversation(owner=OWNER_ID, session_id="example-trip-preference-session")
saved = conversation.user("I am visiting Tokyo and Kyoto next month. I am vegetarian.")
print("Saved event", saved.event_id)
search = {
"query": "What are the requirements for the user's trip?",
"filter_": {
"owner_id": {"eq": OWNER_ID},
"memory_type": {"eq": "trip_preference"},
},
"limit": 5,
}
for _ in range(30): # extraction runs in the background
custom_results = memory.client.search_long_term_memory(request=search)
if custom_results.items:
break
time.sleep(10)
for record in custom_results.items:
print(record.memory_id, record.memory_type, record.attributes)
dietary_requirements = set()
if not custom_results.items:
print("No matching trip preferences yet. Retry after a short wait.")
for record in custom_results.items:
attributes = record.attributes or {}
requirements = attributes.get("dietary_requirements", [])
if isinstance(requirements, list):
dietary_requirements.update(
value for value in requirements if isinstance(value, str)
)
print("Restaurant requirements:", sorted(dietary_requirements))
direct_result = memory.client.bulk_create_long_term_memories(memories=[{
"memory_id": "example-trip-form-1",
"text": "The user plans to visit Tokyo and Kyoto next month and requires vegetarian food.",
"owner_id": OWNER_ID,
"memory_type": "trip_preference",
"attributes": {
"destinations": ["Tokyo", "Kyoto"],
"travel_period": "next month",
"dietary_requirements": ["vegetarian"],
"food_preferences": ["spicy food"],
},
}])
print("Created:", direct_result.created)
for error in direct_result.errors or []:
print("Not created:", error)
jq -n --arg owner "$OWNER_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. I am vegetarian."}],
createdAt: $now,
idempotencyKey: $key
}' > event.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/sessions/example-trip-preference-session/events" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @event.json | jq .
set -euo pipefail
MEMORY_API="${IRIS_AGENT_MEMORY_URL%/}/iris/data/v1/agent-memory/stores/$IRIS_AGENT_MEMORY_STORE_ID"
AUTH="Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY"
OWNER_ID=quickstart-user
jq -n --arg owner "$OWNER_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. I am vegetarian."}],
createdAt: $now,
idempotencyKey: $key
}' > event.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/sessions/example-trip-preference-session/events" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @event.json | jq .
jq -n --arg owner "$OWNER_ID" --arg query "What are the requirements for the user's trip?" '{
query: $query,
filter: {ownerId: {eq: $owner}, memoryType: {eq: "trip_preference"}},
limit: 5
}' > search.json
for _ in $(seq 30); do # extraction runs in the background
RESULTS=$(curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories/search" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @search.json)
[ "$(jq '.items | length' <<< "$RESULTS")" -gt 0 ] && break
sleep 10
done
jq '.items[] | {memoryId, memoryType, attributes}' <<< "$RESULTS"
jq -r '[.items[].attributes.dietary_requirements // empty | .[] | strings] | unique
| if length == 0 then "No matching trip preferences yet. Retry after a short wait."
else "Restaurant requirements: \(join(", "))" end' <<< "$RESULTS"
jq -n --arg owner "$OWNER_ID" --arg key "$(uuidgen)" '{
idempotencyKey: $key,
memories: [{
memoryId: "example-trip-form-1",
text: "The user plans to visit Tokyo and Kyoto next month and requires vegetarian food.",
ownerId: $owner,
memoryType: "trip_preference",
attributes: {
destinations: ["Tokyo", "Kyoto"],
travel_period: "next month",
dietary_requirements: ["vegetarian"],
food_preferences: ["spicy food"]
}
}]
}' > direct.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @direct.json | jq .
Search for the structured memory. The examples search every 10 seconds until extraction creates one:
search = {
"query": "What are the requirements for the user's trip?",
"filter_": {
"owner_id": {"eq": OWNER_ID},
"memory_type": {"eq": "trip_preference"},
},
"limit": 5,
}
for _ in range(30): # extraction runs in the background
custom_results = memory.client.search_long_term_memory(request=search)
if custom_results.items:
break
time.sleep(10)
for record in custom_results.items:
print(record.memory_id, record.memory_type, record.attributes)
jq -n --arg owner "$OWNER_ID" --arg query "What are the requirements for the user's trip?" '{
query: $query,
filter: {ownerId: {eq: $owner}, memoryType: {eq: "trip_preference"}},
limit: 5
}' > search.json
for _ in $(seq 30); do # extraction runs in the background
RESULTS=$(curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories/search" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @search.json)
[ "$(jq '.items | length' <<< "$RESULTS")" -gt 0 ] && break
sleep 10
done
jq '.items[] | {memoryId, memoryType, attributes}' <<< "$RESULTS"
The items array contains records with memoryType set to trip_preference. Custom fields are inside each record's attributes object. For example, a result can include this excerpt:
{
"memoryType": "trip_preference",
"attributes": {
"destinations": ["Tokyo", "Kyoto"],
"travel_period": "next month",
"dietary_requirements": ["vegetarian"],
"food_preferences": ["spicy food"]
}
}
The values depend on the conversation and extraction model. An application can use dietary_requirements to constrain restaurant recommendations, while food_preferences helps rank suitable choices. Check that each field is present and has the expected type before using it.
Inspect the structured fields. The examples collect dietary requirements for the next agent turn:
dietary_requirements = set()
if not custom_results.items:
print("No matching trip preferences yet. Retry after a short wait.")
for record in custom_results.items:
attributes = record.attributes or {}
requirements = attributes.get("dietary_requirements", [])
if isinstance(requirements, list):
dietary_requirements.update(
value for value in requirements if isinstance(value, str)
)
print("Restaurant requirements:", sorted(dietary_requirements))
jq -r '[.items[].attributes.dietary_requirements // empty | .[] | strings] | unique
| if length == 0 then "No matching trip preferences yet. Retry after a short wait."
else "Restaurant requirements: \(join(", "))" end' <<< "$RESULTS"
If results remain empty, check that the type is enabled and that the owner ID matches the stored records. An empty result does not mean the user has no dietary requirements.
Create a custom memory directly
If your application already has structured trip data, write it directly using the registered trip_preference type. This example represents data from a form and uses the same user identifier:
direct_result = memory.client.bulk_create_long_term_memories(memories=[{
"memory_id": "example-trip-form-1",
"text": "The user plans to visit Tokyo and Kyoto next month and requires vegetarian food.",
"owner_id": OWNER_ID,
"memory_type": "trip_preference",
"attributes": {
"destinations": ["Tokyo", "Kyoto"],
"travel_period": "next month",
"dietary_requirements": ["vegetarian"],
"food_preferences": ["spicy food"],
},
}])
print("Created:", direct_result.created)
for error in direct_result.errors or []:
print("Not created:", error)
jq -n --arg owner "$OWNER_ID" --arg key "$(uuidgen)" '{
idempotencyKey: $key,
memories: [{
memoryId: "example-trip-form-1",
text: "The user plans to visit Tokyo and Kyoto next month and requires vegetarian food.",
ownerId: $owner,
memoryType: "trip_preference",
attributes: {
destinations: ["Tokyo", "Kyoto"],
travel_period: "next month",
dietary_requirements: ["vegetarian"],
food_preferences: ["spicy food"]
}
}]
}' > direct.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @direct.json | jq .
Inspect the response's errors array for per-record errors. Run the custom-memory search again to retrieve the record. Direct creation does not wait for background extraction and does not apply the extraction prompt or sensitive-data exclusions. Creating a record whose memoryId already exists fails for that record and leaves the stored memory unchanged; use PUT to replace it. Access checks and protection for document-managed records still apply.
See custom memory types for configuration requirements and limits.
Test semantic exclusions
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.
In the Redis Cloud console, open your service’s Configuration tab and select Edit. Under Sensitive-data exclusions, enable Semantic exclusions and set this prompt:
Do not keep passwords, access tokens, recovery codes, payment card information, or booking confirmation codes in long-term memory.
The semantic exclusion prompt tells Redis Agent Memory which information should not be kept in long-term memory. Add an event containing a fictional booking code and information that is safe to retain:
conversation = memory.conversation(owner=OWNER_ID, session_id=SESSION_ID)
saved = conversation.user(
"I booked Hotel Sakura in Tokyo. For this example, "
"the fictional booking confirmation code is DEMO-7QX9."
)
print("Saved event", saved.event_id)
import time
from redis_iris.agent_memory import AgentMemory
OWNER_ID = "quickstart-user"
SESSION_ID = "travel-planning-session"
memory = AgentMemory.from_env()
conversation = memory.conversation(owner=OWNER_ID, session_id=SESSION_ID)
saved = conversation.user(
"I booked Hotel Sakura in Tokyo. For this example, "
"the fictional booking confirmation code is DEMO-7QX9."
)
print("Saved event", saved.event_id)
question = "Where is the user staying in Tokyo?"
for _ in range(30): # extraction runs in the background
known = memory.recall(question, owner=OWNER_ID)
if any("Sakura" in found.text for found in known):
break
time.sleep(10)
print(known.as_prompt())
jq -n --arg owner "$OWNER_ID" --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg key "$(uuidgen)" '{
actorId: $owner,
role: "USER",
content: [{text: "I booked Hotel Sakura in Tokyo. For this example, the fictional booking confirmation code is DEMO-7QX9."}],
createdAt: $now,
idempotencyKey: $key
}' > event.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/sessions/$SESSION_ID/events" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @event.json | jq .
set -euo pipefail
MEMORY_API="${IRIS_AGENT_MEMORY_URL%/}/iris/data/v1/agent-memory/stores/$IRIS_AGENT_MEMORY_STORE_ID"
AUTH="Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY"
OWNER_ID=quickstart-user
SESSION_ID=travel-planning-session
jq -n --arg owner "$OWNER_ID" --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg key "$(uuidgen)" '{
actorId: $owner,
role: "USER",
content: [{text: "I booked Hotel Sakura in Tokyo. For this example, the fictional booking confirmation code is DEMO-7QX9."}],
createdAt: $now,
idempotencyKey: $key
}' > event.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/sessions/$SESSION_ID/events" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @event.json | jq .
jq -n --arg owner "$OWNER_ID" '{
query: "Where is the user staying in Tokyo?",
filter: {ownerId: {eq: $owner}},
limit: 5
}' > search.json
for _ in $(seq 30); do # extraction runs in the background
RESULTS=$(curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories/search" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @search.json)
jq -e 'any(.items[]; .text | contains("Sakura"))' <<< "$RESULTS" > /dev/null && break
sleep 10
done
jq '.items[] | {memoryId, text}' <<< "$RESULTS"
Search for the safe hotel information. The examples search every 10 seconds until a memory about the hotel appears:
question = "Where is the user staying in Tokyo?"
for _ in range(30): # extraction runs in the background
known = memory.recall(question, owner=OWNER_ID)
if any("Sakura" in found.text for found in known):
break
time.sleep(10)
print(known.as_prompt())
jq -n --arg owner "$OWNER_ID" '{
query: "Where is the user staying in Tokyo?",
filter: {ownerId: {eq: $owner}},
limit: 5
}' > search.json
for _ in $(seq 30); do # extraction runs in the background
RESULTS=$(curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/memories/search" \
-H "$AUTH" -H 'Content-Type: application/json' --data-binary @search.json)
jq -e 'any(.items[]; .text | contains("Sakura"))' <<< "$RESULTS" > /dev/null && break
sleep 10
done
jq '.items[] | {memoryId, text}' <<< "$RESULTS"
What to expect: A memory similar to User booked Hotel Sakura in Tokyo without the fictional confirmation code DEMO-7QX9. If the code appears, refine the exclusion prompt and test again.
Semantic exclusions are advisory and do not guarantee that sensitive information is excluded. Session content still reaches the extraction model provider. Do not use real sensitive data in this exercise. Exclusions do not apply to directly created long-term memories.
See sensitive-data exclusions for configuration details.