Memory & Persistence
When you use LLMs directly they all have a different approach to memory and chat persistence.
Here we standardize on a simple approach
- By default a session always retains its history/context
- To disable that behaviour, set a new session to incognito mode
- Memory is stored at an app + user id level
Let's go over a few examples
Authenticated User
Open a session with a user_id and tell the agent your shoe size.
A week later, in a new session, it still knows.
import { Client, type Session } from "@stream-io/calypso";
const support = new Client().agent("support");
const memory = { user_id: "user-123" };
const monday = await support.sessions.create({ memory });
await ask(monday, "I wear a size 10 shoe.");
await monday.close();
const friday = await support.sessions.create({ memory });
console.log(
await ask(friday, "What size should I order in these running shoes?"),
);
// Size 10, going by what you told me before.
async function ask(session: Session, text: string): Promise<string> {
await session.responses.create(text);
let answer = "";
for await (const event of session.events()) {
if (event.kind === "response_delta") answer += event.text;
if (event.kind === "responded") break;
}
return answer;
}val agents = VisionAgents(apiKey = "your_api_key")
agents.setUser(User(id = "user-123")) { yourBackend.agentToken() }
val support = agents.agent("support")
// Remembers for the user setUser named.
val options = SessionOptions(memory = SessionOptions.Memory())
val monday = support.chat(options)
ask(monday, "I wear a size 10 shoe.")
monday.close()
val friday = support.chat(options)
println(ask(friday, "What size should I order in these running shoes?"))
// Size 10, going by what you told me before.
suspend fun ask(session: AgentSession, text: String): String = coroutineScope {
val answer = async(start = CoroutineStart.UNDISPATCHED) {
session.events()
.takeWhile { it.type != "responded" }
.filter { it.type == "response_delta" }
.fold("") { soFar, event -> soFar + event.text }
}
session.responses.create(text)
answer.await()
}let agents = VisionAgents(apiKey: "your_api_key")
agents.setUser(User(id: "user-123")) { try await yourBackend.agentToken() }
let support = agents.agent("support")
var options = SessionOptions()
// Remembers for the user setUser named.
options.memory = SessionOptions.Memory()
let monday = try await support.sessions.create(options)
_ = try await ask(monday, "I wear a size 10 shoe.")
await monday.close()
let friday = try await support.sessions.create(options)
print(try await ask(friday, "What size should I order in these running shoes?"))
// Size 10, going by what you told me before.
func ask(_ session: AgentSession, _ text: String) async throws -> String {
await session.start()
let events = session.events()
_ = try await session.responses.create(text)
var answer = ""
for await event in events {
if event.kind == .responseDelta { answer += event.text }
if event.kind == .responded { break }
}
return answer
}agent, err := agents.New(agents.Options{
Name: "support",
MemoryFilter: map[string]string{"user_id": "user-123"},
})
if err != nil {
return err
}
monday, err := agent.Chat(ctx)
if err != nil {
return err
}
if _, err := ask(ctx, monday, "I wear a size 10 shoe."); err != nil {
return err
}
if err := monday.Close(ctx); err != nil {
return err
}
friday, err := agent.Chat(ctx)
if err != nil {
return err
}
answer, err := ask(ctx, friday, "What size should I order in these running shoes?")
if err != nil {
return err
}
fmt.Println(answer) // Size 10, going by what you told me before.
func ask(ctx context.Context, session *agents.Session, text string) (string, error) {
if _, err := session.Responses.Create(ctx, text); err != nil {
return "", err
}
var answer strings.Builder
for event := range session.Events() {
switch event.Kind {
case "response_delta":
answer.WriteString(event.Text)
case "responded":
return answer.String(), nil
}
}
return answer.String(), nil
}What the agent learns is saved as the conversation goes, and recalled when a session opens.
Every memory is kept under your app and the user_id, so one person's memories never reach another.
In a browser, as on a phone, pass memory: {}: it remembers for the user setUser named, since a user's device can't name anyone else. A backend names whichever of its users it means, or the one it acts for when it names none.
Incognito
An incognito session saves nothing. Tell it your shoe size and the next session has no idea, and the conversation itself is not stored anywhere.
const memory = { user_id: "user-456" };
const secret = await support.sessions.create({ memory, incognito: true });
await ask(secret, "I wear a size 10 shoe.");
await secret.close();
const later = await support.sessions.create({ memory });
console.log(
await ask(later, "What size should I order in these running shoes?"),
);
// I don't know your shoe size yet. What size do you wear?
await support.sessions.get(secret.id); // throws: not foundagents.setUser(User(id = "user-456")) { yourBackend.agentToken() }
// Remembers for user-456, the user setUser named.
val options = SessionOptions(memory = SessionOptions.Memory())
val secret = support.chat(options.copy(incognito = true))
ask(secret, "I wear a size 10 shoe.")
secret.close()
val later = support.chat(options)
println(ask(later, "What size should I order in these running shoes?"))
// I don't know your shoe size yet. What size do you wear?
support.sessions.get(secret.id) // throws: not foundagents.setUser(User(id: "user-456")) { try await yourBackend.agentToken() }
var options = SessionOptions()
// Remembers for user-456, the user setUser named.
options.memory = SessionOptions.Memory()
var secretOptions = options
secretOptions.incognito = true
let secret = try await support.sessions.create(secretOptions)
_ = try await ask(secret, "I wear a size 10 shoe.")
await secret.close()
let later = try await support.sessions.create(options)
print(try await ask(later, "What size should I order in these running shoes?"))
// I don't know your shoe size yet. What size do you wear?
_ = try await support.sessions.get(secret.id) // throws: not foundmemory := map[string]string{"user_id": "user-456"}
secret, err := agent.Chat(ctx, agents.SessionOptions{MemoryFilter: memory, Incognito: true})
if err != nil {
return err
}
if _, err := ask(ctx, secret, "I wear a size 10 shoe."); err != nil {
return err
}
if err := secret.Close(ctx); err != nil {
return err
}
later, err := agent.Chat(ctx, agents.SessionOptions{MemoryFilter: memory})
if err != nil {
return err
}
answer, err := ask(ctx, later, "What size should I order in these running shoes?")
if err != nil {
return err
}
fmt.Println(answer) // I don't know your shoe size yet. What size do you wear?
api, err := client.New(stream.Backend{})
if err != nil {
return err
}
_, err = api.Agent("support").Sessions.Get(ctx, secret.ID()) // not foundAn incognito session has no transcript, so it can't be listed, searched, forked or rewound. It still recalls what earlier sessions learned about the person; it just adds nothing.
Forgetting
Ending a session keeps what it learned. To forget one session, delete its memories. To forget a person, truncate their memories: everything learned about them, in every session and by every agent, is deleted.
import { Client } from "@stream-io/calypso";
const api = new Client();
await api.agent("support").sessions.deleteMemories(sessionId);
await api.memories.truncate("user-123");api, err := client.New(stream.Backend{})
if err != nil {
return err
}
if err := api.Agent("support").Sessions.DeleteMemories(ctx, sessionID); err != nil {
return err
}
if err := api.Memories().Truncate(ctx, "user-123"); err != nil {
return err
}