# Long-term memory

```json metadata
{
  "schema_version": 2,
  "title": "Long-term memory",
  "description": "Create and search memories that remain useful across conversations.",
  "categories": ["docs","develop","ai"],
  "tableOfContents": {"sections":[{"id":"create-long-term-memories","title":"Create long-term memories"},{"id":"memory-fields","title":"Memory fields"},{"children":[{"id":"search-by-meaning","title":"Search by meaning"},{"id":"filter-and-sort-without-semantic-search","title":"Filter and sort without semantic search"}],"id":"search-long-term-memory","title":"Search long-term memory"},{"children":[{"id":"prevent-lost-updates","title":"Prevent lost updates"}],"id":"update-a-memory","title":"Update a memory"},{"id":"delete-memories","title":"Delete memories"},{"children":[{"id":"define-a-memory-type","title":"Define a memory type"},{"id":"extract-custom-memories","title":"Extract custom memories"},{"id":"create-a-custom-memory-directly","title":"Create a custom memory directly"}],"id":"custom-memory-types","title":"Custom memory types"},{"id":"test-semantic-exclusions","title":"Test semantic exclusions"}]}

,
  "codeExamples": [{"codetabsId":"iris_agent_memory_long_term_memory-stepltm-create","difficulty":"beginner","id":"ltm-create","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory-stepltm-create"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory-stepltm-create"}]},{"codetabsId":"iris_agent_memory_long_term_memory-stepltm-search","difficulty":"beginner","id":"ltm-search","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory-stepltm-search"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory-stepltm-search"},{"clientId":"iris-cli","clientName":"CLI (Iris)","id":"CLI (Iris)","langId":"bash","panelId":"panel_CLI (Iris)_iris_agent_memory_long_term_memory-stepltm-search"}]},{"codetabsId":"iris_agent_memory_long_term_memory-stepltm-filter","difficulty":"beginner","id":"ltm-filter","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory-stepltm-filter"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory-stepltm-filter"}]},{"codetabsId":"iris_agent_memory_long_term_memory_update-stepltm-get","difficulty":"beginner","id":"ltm-get","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory_update-stepltm-get"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory_update-stepltm-get"}]},{"codetabsId":"iris_agent_memory_long_term_memory_update-stepltm-patch","difficulty":"beginner","id":"ltm-patch","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory_update-stepltm-patch"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory_update-stepltm-patch"}]},{"codetabsId":"iris_agent_memory_long_term_memory_update-stepltm-put","difficulty":"beginner","id":"ltm-put","languages":[{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory_update-stepltm-put"}]},{"codetabsId":"iris_agent_memory_long_term_memory_update-stepltm-stale-revision","difficulty":"beginner","id":"ltm-stale-revision","languages":[{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory_update-stepltm-stale-revision"}]},{"codetabsId":"iris_agent_memory_long_term_memory-stepltm-delete","difficulty":"beginner","id":"ltm-delete","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory-stepltm-delete"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory-stepltm-delete"}]},{"codetabsId":"iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-event","difficulty":"beginner","id":"custom-memory-event","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-event"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-event"}]},{"codetabsId":"iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-search","difficulty":"beginner","id":"custom-memory-search","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-search"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-search"}]},{"codetabsId":"iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-fields","difficulty":"beginner","id":"custom-memory-fields","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-fields"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-fields"}]},{"codetabsId":"iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-direct","difficulty":"beginner","id":"custom-memory-direct","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-direct"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory_custom_types-stepcustom-memory-direct"}]},{"codetabsId":"iris_agent_memory_long_term_memory_exclusions-stepexclusion-event","difficulty":"beginner","id":"exclusion-event","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory_exclusions-stepexclusion-event"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory_exclusions-stepexclusion-event"}]},{"codetabsId":"iris_agent_memory_long_term_memory_exclusions-stepexclusion-search","difficulty":"beginner","id":"exclusion-search","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_long_term_memory_exclusions-stepexclusion-search"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_long_term_memory_exclusions-stepexclusion-search"}]}]
}
```

## Code examples

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


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](https://redis.io/docs/latest/operate/iris/agent-memory/create-service#memory-configuration) to configure the extraction cadence and long-term-memory TTL in Redis Cloud.

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.


This example creates three memories for the quickstart user. Direct creation does not wait for background extraction, so the memories are searchable immediately:

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

##### Python (Iris)

```python
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)
```

##### cURL (Iris)

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



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](#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](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/developer-guide#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](#update-a-memory). |
| `documentManaged` | `true` when [MemoryETL](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/memory-etl) 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`](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/api-reference#operation/SearchLongTermMemory).

For domain-specific fields and extraction instructions, see [Custom memory types](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/long-term-memory#custom-memory-types). To group memories in a hierarchy, see [Namespaces](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/namespaces).

For controls that apply during automatic extraction, see [Exclude sensitive data from automatic extraction](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/developer-guide#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:

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

##### Python (Iris)

```python
known = memory.recall(
    "What dietary requirements and food preferences does the user have?",
    owner=OWNER_ID,
    limit=5,
)
print(known.as_prompt())
```

##### cURL (Iris)

```bash
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}'
```

##### CLI (Iris)

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

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

##### Python (Iris)

```python
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)
```

##### cURL (Iris)

```bash
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](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/memory-etl) 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`](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/api-reference#operation/SearchLongTermMemory). 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:

```json
{
  "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}`](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/api-reference#operation/UpdateLongTermMemory) changes only the fields you send: `text`, `memoryType`, `topics`, `namespaceId`, `ownerId`, or `sessionId`. It does not change `attributes`. An update that changes only `namespaceId` keeps the memory's current expiration; any other update restarts the long-term-memory TTL.
* **Replace or create:** [`PUT /memories/{memoryId}`](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/api-reference#operation/UpsertLongTermMemory) creates the memory if it does not exist, or replaces it completely. It requires `text` and `ownerId`. Omitted optional fields, including `attributes`, are cleared, and `memoryType` defaults to `semantic`. Replacement keeps `createdAt` and restarts the TTL. The response's `created` field 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:

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

##### Python (Iris)

```python
current = memory.client.get_long_term_memory(memory_id=MEMORY_ID)
print(current.text, current.topics, current.updated_at)
```

##### cURL (Iris)

```bash
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)
```



Change the text and topics with a partial update:

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

##### Python (Iris)

```python
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)
```

##### cURL (Iris)

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

**Available in:** cURL (Iris)

##### cURL (Iris)

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

**Available in:** cURL (Iris)

##### cURL (Iris)

```bash
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](#create-long-term-memories):

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

##### Python (Iris)

```python
memory.delete(created.id for created in seeded)
print("Deleted", len(seeded), "memories")
```

##### cURL (Iris)

```bash
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](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/quickstart#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](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/long-term-memory#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](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.


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](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/long-term-memory#define-a-memory-type). Use this extraction prompt:

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

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

##### Python (Iris)

```python
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)
```

##### cURL (Iris)

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



Search for the structured memory. The examples search every 10 seconds until extraction creates one:

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

##### Python (Iris)

```python
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)
```

##### cURL (Iris)

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

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

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

##### Python (Iris)

```python
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))
```

##### cURL (Iris)

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

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

##### Python (Iris)

```python
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)
```

##### cURL (Iris)

```bash
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`](#update-a-memory) to replace it. Access checks and protection for document-managed records still apply.

See [custom memory types](https://redis.io/docs/latest/operate/iris/agent-memory/create-service#custom-memory-types) for configuration requirements and limits.

## Test semantic exclusions

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.


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:

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

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

##### Python (Iris)

```python
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)
```

##### cURL (Iris)

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



Search for the safe hotel information. The examples search every 10 seconds until a memory about the hotel appears:

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

##### Python (Iris)

```python
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())
```

##### cURL (Iris)

```bash
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"
```



> [!NOTE]
> **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.

> [!WARNING]
> 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](https://redis.io/docs/latest/operate/iris/agent-memory/create-service#sensitive-data-exclusions) for configuration details.

