# Summarize long-term memories with Summary Views

```json metadata
{
  "schema_version": 2,
  "title": "Summarize long-term memories with Summary Views",
  "description": "Create owned summaries of long-term memories, refresh them, and read text or structured results.",
  "categories": ["docs","develop","ai"],
  "tableOfContents": {"sections":[{"children":[{"id":"choose-private-or-shared-visibility","title":"Choose private or shared visibility"}],"id":"prepare-the-service-and-identity","title":"Prepare the service and identity"},{"children":[{"id":"handle-asynchronous-result-states","title":"Handle asynchronous result states"}],"id":"create-refresh-and-read-a-view","title":"Create, refresh, and read a view"},{"id":"filter-and-group-sources","title":"Filter and group sources"},{"id":"choose-structured-output-and-continuous-refresh","title":"Choose structured output and continuous refresh"},{"id":"check-coverage-and-manage-definitions","title":"Check coverage and manage definitions"}]}

,
  "codeExamples": [{"codetabsId":"iris_agent_memory_summary_views-stepsummary-view-seed","difficulty":"beginner","id":"summary-view-seed","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_summary_views-stepsummary-view-seed"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_summary_views-stepsummary-view-seed"}]},{"codetabsId":"iris_agent_memory_summary_views-stepsummary-view-create","difficulty":"beginner","id":"summary-view-create","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_summary_views-stepsummary-view-create"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_summary_views-stepsummary-view-create"}]},{"codetabsId":"iris_agent_memory_summary_views-stepsummary-view-refresh","difficulty":"beginner","id":"summary-view-refresh","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_summary_views-stepsummary-view-refresh"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_summary_views-stepsummary-view-refresh"}]},{"codetabsId":"iris_agent_memory_summary_views-stepsummary-view-result","difficulty":"beginner","id":"summary-view-result","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_summary_views-stepsummary-view-result"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_summary_views-stepsummary-view-result"}]},{"codetabsId":"iris_agent_memory_summary_views-stepsummary-view-update","difficulty":"beginner","id":"summary-view-update","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_summary_views-stepsummary-view-update"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_summary_views-stepsummary-view-update"}]},{"codetabsId":"iris_agent_memory_summary_views-stepsummary-view-delete","difficulty":"beginner","id":"summary-view-delete","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_agent_memory_summary_views-stepsummary-view-delete"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_agent_memory_summary_views-stepsummary-view-delete"}]}]
}
```

## Code examples

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


A Summary View turns selected long-term memories into a reusable text or structured summary. For example, a travel assistant can maintain a compact profile instead of adding every matching memory to each model request.

A **definition** specifies the sources, filters, grouping, output, and refresh policy. A **partition** is one materialized result for an exact combination of grouping values. An ungrouped definition has one result. Results are derived data; they do not enter long-term-memory search or replace the source records.

Summary Views differ from [session summarization](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/sessions#configure-automatic-summarization), which compacts conversation events. The supported source is `LONG_TERM`. Document-managed MemoryETL records are excluded from Summary View sources.

## Prepare the service and identity

Summary Views are available in Redis Cloud and enabled in self-managed deployments. For self-managed model and capacity settings, see [Configure Summary Views](https://redis.io/docs/latest/operate/iris/agent-memory/self-managed/configuration#enable-summary-views).

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.


The server assigns the definition's immutable `ownerId` from the caller's authenticated user ID. A create request cannot supply an owner field.

In on-prem deployments with ownership enforcement enabled, creating a view requires an authenticated user ID, including when an agent key is used. Use [delegated user identity](https://redis.io/docs/latest/operate/iris/agent-memory/self-managed/authentication#delegate-a-user-with-an-agent-key) and send the user token on every Summary View request: pass `http_headers={"X-Iris-User-Token": user_token}` to each Python call, or add `-H "X-Iris-User-Token: $USER_TOKEN"` to each cURL request. The verified user becomes the owner, so filter sources by that user's ID.

In on-prem deployments with `ownership.enabled: false`, agent keys without a user can create, refresh, and read views of either visibility. `ownerId` is empty when the caller has no authenticated user ID.

### Choose private or shared visibility

In on-prem deployments with ownership enforcement enabled:

| Visibility | Who can read | Sources |
|:-----------|:-------------|:--------|
| `PRIVATE` (default) | The definition owner. | The owner's personal and store-authorized shared memories, narrowed by the definition's filters. |
| `SHARED` | Callers with store read access. The owner retains lifecycle control. | Shared memories only. Personal records cannot be published through a shared view. |

Shared does not mean a particular team. Enforce team membership in your backend or use separate stores and grants.

In on-prem deployments with enforcement off, both visibilities use store-level access without ownership checks. `PRIVATE` does not restrict readers, and `SHARED` views are not restricted to shared memories. Source selection for either visibility does not check record ownership or namespace scope, including personal namespace filters. `PRIVATE` does not replace backend authorization. A view records its source-access policy at creation. Changing on-prem enforcement requires replacing existing views and configuring server and worker alike.

## Create, refresh, and read a view

These examples create one private profile for the quickstart user, request a full refresh, and poll its result. First create ordinary long-term memories for that user. For more ways to write them, see [Create a custom memory directly](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/long-term-memory#create-a-custom-memory-directly):

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

##### Python (Iris)

```python
memory.remember("The user is vegetarian and prefers spicy food.", owner=OWNER_ID)
memory.remember("The user wants to visit Tokyo and Kyoto next month.", owner=OWNER_ID)
```

##### cURL (Iris)

```bash
jq -n --arg owner "$OWNER_ID" --arg key "$(uuidgen)" '{
  memories: [
    {memoryId: "example-diet", ownerId: $owner, text: "The user is vegetarian and prefers spicy food."},
    {memoryId: "example-trip", ownerId: $owner, text: "The user wants to visit Tokyo and Kyoto next month."}
  ],
  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 .
```



Create the view and save its ID for later requests:

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

##### Python (Iris)

```python
view = memory.client.create_summary_view(
    name="example-travel-profile",
    source=models.SummaryViewSource.LONG_TERM,
    visibility=models.SummaryViewVisibility.PRIVATE,
    group_by=[],
    filter_={"owner_id": {"eq": OWNER_ID}},
    output={
        "format_": "TEXT",
        "instructions": "Summarize the travel preferences in these memories.",
    },
    refresh_policy={"mode": "MANUAL"},
)
view_id = view.view_id
print("View ID:", view_id, "owner:", repr(view.owner_id))
```

##### cURL (Iris)

```bash
jq -n --arg owner "$OWNER_ID" '{
  name: "example-travel-profile",
  source: "LONG_TERM",
  visibility: "PRIVATE",
  groupBy: [],
  filter: {ownerId: {eq: $owner}},
  output: {format: "TEXT", instructions: "Summarize the travel preferences in these memories."},
  refreshPolicy: {mode: "MANUAL"}
}' > view.json
VIEW_ID=$(curl --fail-with-body --silent --show-error \
  -X POST "$MEMORY_API/summary-views" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' --data-binary @view.json | jq -er .viewId)
echo "View ID: $VIEW_ID"
```



Request a full refresh. Creation and refresh are asynchronous; their responses do not contain generated output:

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

##### Python (Iris)

```python
accepted = memory.client.refresh_summary_view(
    view_id=view_id, target=models.SummaryViewRefreshTarget.FULL,
)
print("Refresh accepted:", accepted.accepted)
```

##### cURL (Iris)

```bash
curl --fail-with-body --silent --show-error \
  -X POST "$MEMORY_API/summary-views/$VIEW_ID/refresh" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' --data '{"target": "FULL"}' | jq .
```



Poll the result until it is ready. A successful refresh with no matching sources returns `EMPTY`:

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

##### Python (Iris)

```python
Status = models.SummaryViewResultStatus
for _ in range(60):  # generation runs in the background
    result = memory.client.get_summary_view_result(
        view_id=view_id, include_source_memory_ids=True,
    )
    if result.status == Status.FAILED:
        raise RuntimeError(f"Refresh failed: {result.error_code}")
    if result.status == Status.EMPTY:
        print("No matching source memories.")
        break
    if result.status == Status.READY and not result.stale and result.value is not None:
        print(result.value.text)
        print(f"{result.summarized_memory_count} of {result.source_memory_count} memories,",
              "truncated:", result.truncated)
        break
    time.sleep(5)
else:
    raise TimeoutError(f"Continue polling view {view_id}")
```

##### cURL (Iris)

```bash
for _ in $(seq 60); do  # generation runs in the background
  RESULT=$(curl --fail-with-body --silent --show-error --get \
    "$MEMORY_API/summary-views/$VIEW_ID/result" \
    -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
    --data-urlencode 'includeSourceMemoryIds=true')
  case "$(jq -r .status <<< "$RESULT")" in
    FAILED) jq . <<< "$RESULT"; exit 1 ;;
    EMPTY) echo "No matching source memories."; break ;;
    READY) if jq -e '.stale == false and .value != null' <<< "$RESULT" > /dev/null; then
        jq '{text: .value.text, sourceMemoryCount, summarizedMemoryCount, truncated}' <<< "$RESULT"
        break
      fi ;;
  esac
  sleep 5
done
```



Text output uses `value.text`; structured output uses `value.data`. Python output configuration uses `format_` and `schema_`, serialized as `format` and `schema`. Do not assume every result has a `value`.

### Handle asynchronous result states

| Status | Application behavior |
|:-------|:---------------------|
| `COLD` | No result is stored for this group. It may not have been generated yet, or it may have been evicted. |
| `PENDING` | Work is queued. Previous output may be withheld because its sources changed or became inaccessible. |
| `READY` | A value is available. Check `stale` and coverage fields. |
| `EMPTY` | No sources match. There is no generated value. |
| `REFRESHING` | New work is running. A last good value can remain. |
| `FAILED` | Inspect `errorCode`. A last good value may remain, or `value` may be absent. |

`stale` is independent of status. It compares the result with the current definition and source changes. Decide whether to use a last good stale value, wait, or fall back to direct recall. Do not turn an absent value into an empty summary.

Result reads validate contributing source records. Deleted, changed, or newly inaccessible sources can cause old output to be withheld and repair to be requested. Repair is asynchronous; an old value need not remain available.

## Filter and group sources

Use `filter` for owners, sessions, stable namespace IDs, topics, memory types, creation times, and typed attributes. `filter.op` defaults to `ALL`; `ANY` combines optional filters with OR. Exact partition scope and time-window restrictions apply separately, as do ownership checks in on-prem deployments. Legacy `namespace` labels are unsupported for Summary Views; use `namespaceId`.

`groupBy` accepts up to four distinct fields: `ownerId`, `sessionId`, `namespaceId`, and `memoryType`. A result read or `target: PARTITION` refresh must supply exactly those fields. Missing or extra scope fields are invalid. `groupBy: []` creates a singleton; omit all scope fields when reading it.

For `groupBy: ["ownerId", "namespaceId"]`, use this partition refresh body:

```json
{
  "target": "PARTITION",
  "scope": {"ownerId":"<user-id>","namespaceId":"<namespace-id>"}
}
```

Send the same scope as query parameters on result reads. `target: FULL` reconciles current and previous partitions.

`evictionPolicy.maxPartitions` bounds grouped results within the deployment limit. Grouped views use the operator's partition limit when the policy is omitted; singleton views omit it. Matching source mutations make a scope recent; reads do not. Evicted scopes can return `COLD`. `timeWindowSeconds` selects sources by creation time in a rolling window; it does not change source retention.

## Choose structured output and continuous refresh

For structured output, replace `output` with:

```json
{
  "format": "STRUCTURED",
  "instructions": "Summarize only preferences supported by the selected memories.",
  "schema": {
    "fields": [
      {"name":"cities","type":"STRING_LIST","description":"Cities the user wants to visit"},
      {"name":"vegetarian","type":"BOOLEAN","description":"Whether the user has stated a vegetarian diet"}
    ]
  }
}
```

The schema accepts 1–50 fields of type `STRING`, `INTEGER`, `FLOAT`, `BOOLEAN`, `STRING_LIST`, `FLOAT_LIST`, or `OBJECT`. Generated fields are optional. Arbitrary JSON Schema, required-field declarations, and nested object schemas are unsupported. Text output must omit `schema`; structured output requires it. Invalid generated data is reported as `OUTPUT_VALIDATION_FAILED`.

For continuous refresh, use:

```json
{"mode":"CONTINUOUS","debounceSeconds":5,"reconcileIntervalSeconds":60}
```

The reconcile interval is required and must fit deployment limits. Debounce coalesces mutations; reconciliation also handles changes such as a moving time window. Manual mode omits both interval fields. Both modes run asynchronously within queue, source, generation, and concurrency limits.

## Check coverage and manage definitions

Inspect `sourceMemoryCount` and `summarizedMemoryCount`. Selection can omit whole records (`sourcesOmitted`) or clip included content (`sourceContentTruncated`). `truncated` combines both. Check `truncated` even when every record was included: some text may have been cut. Concurrent writes can change the source set while counts are measured.

Request `includeSourceMemoryIds=true` for the selected source IDs. These identify contributing records, not every match. `computedAt`, `sourceUpdatedAt`, and `definitionVersion` help assess freshness.

| Action | Python method | REST |
|:-------|:--------------|:-----|
| List definitions | `memory.client.list_summary_views` | `GET /summary-views` |
| Read a definition | `memory.client.get_summary_view` | `GET /summary-views/{viewId}` |
| Update a definition | `memory.client.update_summary_view` | `PATCH /summary-views/{viewId}` |
| Delete a definition | `memory.client.delete_summary_view` | `DELETE /summary-views/{viewId}` |

REST paths are relative to `/iris/data/v1/agent-memory/stores/{storeId}`. Lists return `items` and optional `nextCursor`; pass it as `cursor` with the same query. Definition states (`ACTIVE`, `DISABLED`, `DELETING`) are separate from result status. Each Python method also has an `_async` variant.

This example updates the description, which does not trigger generation, and lists the store's definitions:

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

##### Python (Iris)

```python
updated = memory.client.update_summary_view(
    view_id=view_id, description="Travel preferences for trip planning.",
)
print(updated.name, "version", updated.definition_version)
for item in memory.client.list_summary_views(limit=20).items:
    print(item.view_id, item.name, item.state)
```

##### cURL (Iris)

```bash
curl --fail-with-body --silent --show-error \
  -X PATCH "$MEMORY_API/summary-views/$VIEW_ID" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"description": "Travel preferences for trip planning."}' | jq '{name, definitionVersion}'
curl --fail-with-body --silent --show-error --get \
  "$MEMORY_API/summary-views" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" \
  --data-urlencode 'limit=20' | jq '.items[] | {viewId, name, state}'
```



| Change | Effect |
|:-------|:-------|
| Name or description | Does not trigger generation. |
| Filter, output, or refresh policy | Increments `definitionVersion`. |
| `groupBy` | Increments `definitionVersion` and replaces the partition layout. |
| Owner, visibility, or source | Cannot be changed. Create a new definition. |
| `state: DISABLED` | Disables the view. Re-enabling requests refresh work. |

`clearFilter: true` and `clearTimeWindow: true` remove those settings; do not also supply replacements.

Deletion returns `202` while cleanup runs. Repeated deletion is idempotent; the definition becomes `DELETING` before schedules and results are removed. Source memories remain.

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

##### Python (Iris)

```python
memory.client.delete_summary_view(view_id=view_id)
```

##### cURL (Iris)

```bash
curl --fail-with-body --silent --show-error \
  -X DELETE "$MEMORY_API/summary-views/$VIEW_ID" \
  -H "Authorization: Bearer $IRIS_AGENT_MEMORY_API_KEY" | jq .
```



For quota failures, inspect the API error and deployment limits. For generation or source-access failures, keep the view ID and address the reported error instead of creating duplicates. [Ingestion webhooks](https://redis.io/docs/latest/develop/ai/context-engine/agent-memory/ingestion-webhooks) can reject generated content before publication.

