Sessions
Add and retrieve conversation events, configure retention, and summarize older messages.
Session memory holds a conversation's transcript. Your application saves each message as an event, then retrieves the transcript when the user sends the next message. This gives the agent the history it needs to continue the conversation.
For example, a travel assistant can retrieve the cities a user mentioned earlier in the chat instead of asking again. Each chat has a session ID (sessionId) that ties its events together. The same ID lets your application retrieve the conversation later.
This page covers adding events, retrieving a conversation, and finding session IDs by owner or namespace. It also explains two settings that control the history: retention determines how long a session stays available, and summarization condenses older messages so they take less space in the model's context window.
Add session events
A session event represents one message from a user, assistant, or system. To append one, send POST /iris/data/v1/agent-memory/stores/{storeId}/sessions/{sessionId}/events, which creates the session if it does not exist. In Python, memory.conversation(owner=..., session_id=...) returns a conversation whose user and assistant methods append events. To start a session with a server-generated ID, send its first event to POST /iris/data/v1/agent-memory/stores/{storeId}/sessions instead. In Python, omit session_id and the SDK generates one; read it from conversation.session_id.
Redis Agent Memory adds the event ID and storage timestamp. The event fields are:
| Field | Who supplies it | Purpose |
|---|---|---|
eventId |
Server-generated. | Identifies the event. |
sessionId |
Client-supplied in the path, or server-generated when you create a session without one. | Identifies the session that contains the event. |
actorId |
Required from the client. | Identifies the user, agent, or other actor that produced the event. |
role |
Required from the client. | Role of the message in the conversation. |
content |
Required from the client. | Message content, including its text. |
createdAt |
Required from the client. | Time the event occurred in the application. |
systemTimestamp |
Server-generated. | Time Redis Agent Memory stored the event. |
metadata |
Optional from the client. | Application-specific information associated with the event. |
- Set the first event's
actorIdto the signed-in user's account ID from your application. Use the same ID each time this user returns. The server uses it as the session owner. Later events can identify other actors, such as the assistant. In Python,conversation.usersends theownerasactorId, andconversation.assistantsends theassistant_idpassed toAgentMemory.from_env(). - Supply a
sessionId, or omit it to let the server create one. Save the returnedevent.sessionIdfor later requests. See Identify users and conversations for examples. - Set the message's
role,content, andcreatedAttimestamp. - Send the request.
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.
conversation = memory.conversation(owner=OWNER_ID, session_id=SESSION_ID)
saved = conversation.user("I am vegetarian.")
print("Saved event", saved.event_id, "in", saved.session_id)
import time
from redis_iris.agent_memory import AgentMemory, models
OWNER_ID = "quickstart-user"
SESSION_ID = "travel-planning-session"
memory = AgentMemory.from_env(assistant_id="travel-agent")
conversation = memory.conversation(owner=OWNER_ID, session_id=SESSION_ID)
saved = conversation.user("I am vegetarian.")
print("Saved event", saved.event_id, "in", saved.session_id)
transcript = conversation.transcript()
if transcript.owner != OWNER_ID:
raise PermissionError("This session belongs to another user.")
print("Summary:", transcript.summary)
for event in transcript.events:
print(event.role, event.actor_id, event.text)
for session_id in memory.sessions(OWNER_ID):
print(session_id)
created = memory.client.create_namespace(
request={
"name": "example-travel",
"scope": models.NamespaceScope.PERSONAL,
"owner_id": OWNER_ID,
}
)
NAMESPACE_ID = created.namespace.namespace_id
trip = memory.conversation(
owner=OWNER_ID, session_id="example-kyoto-trip", namespace_id=NAMESPACE_ID
)
trip.user("Plan two days in Kyoto.")
page = memory.client.list_sessions(namespace_id=NAMESPACE_ID)
for session in page.items:
print(session.session_id, session.owner_id, session.namespace_id)
page = memory.client.list_sessions(namespace_id=NAMESPACE_ID, owner_id=OWNER_ID)
for session in page.items:
print(session.session_id, session.updated_at)
page = memory.client.list_sessions(include_all=True, limit=20)
for session in page.items:
print(session.session_id, session.owner_id)
print("Next page:", page.next_cursor)
turns = [
("assistant", "What dates are you traveling?"),
("user", "I arrive on October 10 and leave on October 18."),
("assistant", "Would you like formal or casual restaurants?"),
("user", "Mostly casual places near public transit."),
("assistant", "Do you have a preferred budget?"),
("user", "About 40 euros per person."),
]
for role, text in turns:
add = conversation.user if role == "user" else conversation.assistant
add(text)
for _ in range(12): # summarization runs in the background
compacted = conversation.transcript()
if compacted.summary:
break
time.sleep(10)
print("Summary:", compacted.summary)
print("Summarized up to event:", compacted.summarized_up_to)
for role, text in compacted.turns:
print(role, text)
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 vegetarian."}],
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 am vegetarian."}],
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 .
curl --fail-with-body --silent --show-error \
"$MEMORY_API/sessions/$SESSION_ID" -H "$AUTH" |
jq '{ownerId, summary: .summary.text, events: [.events[] | {role, actorId, content}]}'
curl --fail-with-body --silent --show-error --get \
"$MEMORY_API/sessions" -H "$AUTH" \
--data-urlencode "ownerId=$OWNER_ID" | jq '.items[].sessionId'
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 "$AUTH" \
-H 'Content-Type: application/json' --data-binary @namespace.json |
jq -r '.namespace.namespaceId')
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: "Plan two days in Kyoto."}],
createdAt: $now,
namespaceId: $ns,
idempotencyKey: $key
}' > trip.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/sessions/example-kyoto-trip/events" -H "$AUTH" \
-H 'Content-Type: application/json' --data-binary @trip.json > /dev/null
curl --fail-with-body --silent --show-error --get \
"$MEMORY_API/sessions" -H "$AUTH" \
--data-urlencode "namespaceId=$NAMESPACE_ID" | jq '.items'
curl --fail-with-body --silent --show-error --get \
"$MEMORY_API/sessions" -H "$AUTH" \
--data-urlencode "namespaceId=$NAMESPACE_ID" \
--data-urlencode "ownerId=$OWNER_ID" | jq '.items'
curl --fail-with-body --silent --show-error --get \
"$MEMORY_API/sessions" -H "$AUTH" \
--data-urlencode 'includeAll=true' --data-urlencode 'limit=20' |
jq '{sessions: [.items[] | {sessionId, ownerId}], nextCursor}'
add_event() { # add_event ROLE ACTOR_ID TEXT
jq -n --arg role "$1" --arg actor "$2" --arg text "$3" \
--arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg key "$(uuidgen)" \
'{actorId: $actor, role: $role, content: [{text: $text}], createdAt: $now, idempotencyKey: $key}' |
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/sessions/$SESSION_ID/events" -H "$AUTH" \
-H 'Content-Type: application/json' --data-binary @- | jq -c '.event | {sessionId, eventId}'
}
add_event ASSISTANT travel-agent "What dates are you traveling?"
add_event USER "$OWNER_ID" "I arrive on October 10 and leave on October 18."
add_event ASSISTANT travel-agent "Would you like formal or casual restaurants?"
add_event USER "$OWNER_ID" "Mostly casual places near public transit."
add_event ASSISTANT travel-agent "Do you have a preferred budget?"
add_event USER "$OWNER_ID" "About 40 euros per person."
for _ in $(seq 12); do # summarization runs in the background
SESSION=$(curl --fail-with-body --silent --show-error \
"$MEMORY_API/sessions/$SESSION_ID" -H "$AUTH")
[ "$(jq '.summary != null' <<< "$SESSION")" = true ] && break
sleep 10
done
jq '{summary, events: [.events[] | {role, content}]}' <<< "$SESSION"
Send each new message once, using the same session ID throughout the conversation.
The session's ownerId comes from the first event's actorId and is returned when you retrieve the session. In on-prem deployments with server-side ownership enforcement, the first event's actorId must match the authenticated user ID.
Retrieve conversation context
When the next message arrives, retrieve the session by its ID before calling the model:
transcript = conversation.transcript()
if transcript.owner != OWNER_ID:
raise PermissionError("This session belongs to another user.")
print("Summary:", transcript.summary)
for event in transcript.events:
print(event.role, event.actor_id, event.text)
curl --fail-with-body --silent --show-error \
"$MEMORY_API/sessions/$SESSION_ID" -H "$AUTH" |
jq '{ownerId, summary: .summary.text, events: [.events[] | {role, actorId, content}]}'
iris agent-memory sessions show travel-planning-session
set -euo pipefail
iris agent-memory sessions show travel-planning-session
iris agent-memory sessions list --owner quickstart-user
iris agent-memory sessions show travel-planning-session
The events array contains the retained conversation messages. If older events have been summarized, the response also includes summary.text. Pass both to the model with the current user message; in Python, transcript.as_messages() returns them as chat messages. See Build context for each agent turn for an example.
Your application must check that the current user can access this session before using it. The request selects a session by ID; it does not include an owner filter. Compare the returned ownerId (transcript.owner in Python) with the authenticated user, as the example does.
List sessions
To let a user reopen an earlier conversation, list their sessions and retrieve the selected session by ID. Use GET /iris/data/v1/agent-memory/stores/{storeId}/sessions, memory.sessions(owner) or memory.client.list_sessions in Python, or iris agent-memory sessions list. These rows do not contain events or generated summary text. See List and sort sessions for SDK examples and pagination.
Filter the list by owner or namespace.
For a user's personal conversations, set ownerId to that user's authenticated ID. A namespace-only query is suitable for shared conversations only after your application checks access to that namespace. Reserve includeAll=true for callers your application authorizes to list the whole store. See Authorize every memory request.
To list one user's session IDs:
for session_id in memory.sessions(OWNER_ID):
print(session_id)
curl --fail-with-body --silent --show-error --get \
"$MEMORY_API/sessions" -H "$AUTH" \
--data-urlencode "ownerId=$OWNER_ID" | jq '.items[].sessionId'
iris agent-memory sessions list --owner quickstart-user
To list sessions in one namespace, filter by its stable ID. This example creates a personal namespace, starts a session in it, and lists that namespace's sessions. In an application, create the namespace once and save its ID.
created = memory.client.create_namespace(
request={
"name": "example-travel",
"scope": models.NamespaceScope.PERSONAL,
"owner_id": OWNER_ID,
}
)
NAMESPACE_ID = created.namespace.namespace_id
trip = memory.conversation(
owner=OWNER_ID, session_id="example-kyoto-trip", namespace_id=NAMESPACE_ID
)
trip.user("Plan two days in Kyoto.")
page = memory.client.list_sessions(namespace_id=NAMESPACE_ID)
for session in page.items:
print(session.session_id, session.owner_id, session.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 "$AUTH" \
-H 'Content-Type: application/json' --data-binary @namespace.json |
jq -r '.namespace.namespaceId')
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: "Plan two days in Kyoto."}],
createdAt: $now,
namespaceId: $ns,
idempotencyKey: $key
}' > trip.json
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/sessions/example-kyoto-trip/events" -H "$AUTH" \
-H 'Content-Type: application/json' --data-binary @trip.json > /dev/null
curl --fail-with-body --silent --show-error --get \
"$MEMORY_API/sessions" -H "$AUTH" \
--data-urlencode "namespaceId=$NAMESPACE_ID" | jq '.items'
namespaceId matches the namespace ID exactly, including case. It excludes descendant namespaces, sessions with only the older namespace string label, and sessions without a namespace resource.
Add ownerId to return sessions that match both the namespace and the owner:
page = memory.client.list_sessions(namespace_id=NAMESPACE_ID, owner_id=OWNER_ID)
for session in page.items:
print(session.session_id, session.updated_at)
curl --fail-with-body --silent --show-error --get \
"$MEMORY_API/sessions" -H "$AUTH" \
--data-urlencode "namespaceId=$NAMESPACE_ID" \
--data-urlencode "ownerId=$OWNER_ID" | jq '.items'
To list all sessions, omit both filters and set includeAll=true:
page = memory.client.list_sessions(include_all=True, limit=20)
for session in page.items:
print(session.session_id, session.owner_id)
print("Next page:", page.next_cursor)
curl --fail-with-body --silent --show-error --get \
"$MEMORY_API/sessions" -H "$AUTH" \
--data-urlencode 'includeAll=true' --data-urlencode 'limit=20' |
jq '{sessions: [.items[] | {sessionId, ownerId}], nextCursor}'
Do not combine includeAll=true with either filter. A request without a filter must set includeAll=true.
The response contains session rows in items and no total count. If the response includes nextCursor, pass it as cursor with the same filters to fetch the next page. See the List Sessions API reference for pagination limits.
Configure session retention
The session-memory time to live (TTL) controls how long sessions remain available. Choose a duration that lets users resume conversations for as long as your application needs. When a session expires, its events are no longer available through session-memory retrieval.
In the Redis Cloud console, open your service's Configuration tab and select Edit. Set Short-term TTL under Memory configuration.
See memory configuration to configure the session-memory TTL in Redis Cloud.
Configure automatic summarization
As a conversation grows, its messages take more space in the model's context window. Automatic summarization replaces older messages with a shorter summary while keeping recent messages in full.
In the Redis Cloud console, open your service's Configuration tab and select Edit. Under Memory configuration, enable Automatic summarization and set:
- Summarize after: The number of messages a session can contain before older messages are summarized.
- Keep most recent: The number of recent messages that remain available in full.
For example, with Summarize after set to 20 and Keep most recent set to 10, Redis Agent Memory summarizes the older 10 messages when the session reaches 20 messages and retains the 10 most recent messages in full.
See automatic summarization to enable summarization and configure both thresholds in Redis Cloud.
For an example that combines session events with long-term recall, follow the developer guide. For complete schemas, see the session-memory application programming interface (API) reference.
To group a session and its extracted memories, use an optional namespace.
Try automatic summarization
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.
Follow Configure automatic summarization. Set Summarize after to 6 and Keep most recent to 2 for this example. The retrieved session will contain a summary object and the two most recent events in events.
Add conversation turns
Add these events to the session from the quickstart to continue the conversation past the configured threshold:
turns = [
("assistant", "What dates are you traveling?"),
("user", "I arrive on October 10 and leave on October 18."),
("assistant", "Would you like formal or casual restaurants?"),
("user", "Mostly casual places near public transit."),
("assistant", "Do you have a preferred budget?"),
("user", "About 40 euros per person."),
]
for role, text in turns:
add = conversation.user if role == "user" else conversation.assistant
add(text)
add_event() { # add_event ROLE ACTOR_ID TEXT
jq -n --arg role "$1" --arg actor "$2" --arg text "$3" \
--arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg key "$(uuidgen)" \
'{actorId: $actor, role: $role, content: [{text: $text}], createdAt: $now, idempotencyKey: $key}' |
curl --fail-with-body --silent --show-error \
-X POST "$MEMORY_API/sessions/$SESSION_ID/events" -H "$AUTH" \
-H 'Content-Type: application/json' --data-binary @- | jq -c '.event | {sessionId, eventId}'
}
add_event ASSISTANT travel-agent "What dates are you traveling?"
add_event USER "$OWNER_ID" "I arrive on October 10 and leave on October 18."
add_event ASSISTANT travel-agent "Would you like formal or casual restaurants?"
add_event USER "$OWNER_ID" "Mostly casual places near public transit."
add_event ASSISTANT travel-agent "Do you have a preferred budget?"
add_event USER "$OWNER_ID" "About 40 euros per person."
Run these writes once. Summarization runs in the background.
Retrieve the summarized session
Retrieve the session again. The Python and cURL examples check every 10 seconds, for up to two minutes, until a summary appears:
for _ in range(12): # summarization runs in the background
compacted = conversation.transcript()
if compacted.summary:
break
time.sleep(10)
print("Summary:", compacted.summary)
print("Summarized up to event:", compacted.summarized_up_to)
for role, text in compacted.turns:
print(role, text)
for _ in $(seq 12); do # summarization runs in the background
SESSION=$(curl --fail-with-body --silent --show-error \
"$MEMORY_API/sessions/$SESSION_ID" -H "$AUTH")
[ "$(jq '.summary != null' <<< "$SESSION")" = true ] && break
sleep 10
done
jq '{summary, events: [.events[] | {role, content}]}' <<< "$SESSION"
iris agent-memory sessions show travel-planning-session
If summary is still missing, run the retrieval again. Compare summary.text with the recent events. The summary should preserve earlier trip decisions while recent turns remain available in full.
What to expect: A summary object that preserves details such as Tokyo, Kyoto, the travel dates, and food preferences. summarizedUpToEventId identifies the last event covered by the summary, while events contains the newer turns that remain in full. The exact summary text can vary.