Chapter 3: Memory and Session (Persistent Conversations)
Goal of this chapter: persist conversation history and support session recovery across processes.
Important: business-layer concepts vs framework concepts
The Memory, Session, and Store described in this chapter are business-layer concepts, and not core components of the Eino framework.
In other words, Eino is responsible for “how to process messages”, while “how to store messages” is entirely up to your application. The implementation shown here is a simple reference; you can choose a completely different storage solution (database, Redis, cloud storage, etc.).
Code Location
- Entry code: cmd/ch03/main.go
- Memory implementation: mem/store.go
Prerequisites
Same as Chapter 1: configure a working ChatModel (OpenAI or Ark).
Run
From examples/quickstart/chatwitheino:
# Create a new session
go run ./cmd/ch03
# Resume an existing session
go run ./cmd/ch03 --session <session-id>
Example output:
Created new session: 083d16da-6b13-4fe6-afb0-c45d8f490ce1
Session title: New Session
Enter your message (empty line to exit):
you> Hello, I am Zhang San
[assistant] Hi Zhang San! Nice to meet you...
you> What is my name?
[assistant] Your name is Zhang San...
Session saved: 083d16da-6b13-4fe6-afb0-c45d8f490ce1
Resume with: go run ./cmd/ch03 --session 083d16da-6b13-4fe6-afb0-c45d8f490ce1
From In-Memory to Persistence: Why We Need “Memory”
In Chapter 2 we implemented multi-turn chat, but there is a problem: the conversation history exists only in memory.
Limitations of in-memory storage:
- history is lost when the process exits
- sessions cannot be recovered across devices/processes
- session management (list/delete/search, etc.) is hard to implement
Positioning of “Memory”:
- Memory is persistent storage for chat history: save conversations to disk or a database
- Memory enables session management: each Session represents one complete conversation
- Memory is decoupled from Agent: the Agent does not care about storage details, only the message list
Analogy:
- in-memory = “scratch paper” (gone when the process exits)
- Memory = “notebook” (saved permanently, can be reviewed anytime)
Key Concepts
Reminder: the Session/Store concepts below are business-layer implementations used to manage storage of chat history. Eino itself does not provide these components. Your application manages the message list, then passes it to
adk.Runnerfor processing.
Session (Business-Layer Concept)
Session represents a complete conversation:
type Session struct {
ID string
CreatedAt time.Time
messages []*schema.Message // conversation history
// ...
}
Core methods:
Append(msg): append a message into the session and persist itGetMessages(): get all messagesTitle(): derive a title from the first user message
Store (Business-Layer Concept)
Store manages persistent storage for multiple sessions:
type Store struct {
dir string // storage directory
cache map[string]*Session // in-memory cache
}
Core methods:
GetOrCreate(id): get or create a sessionList(): list all sessionsDelete(id): delete a session
JSONL File Format
Each Session is stored as a .jsonl file:
{"type":"session","id":"083d16da-...","created_at":"2026-03-11T10:00:00Z"}
{"role":"user","content":"Hello, who am I?"}
{"role":"assistant","content":"Hi! I don’t know who you are yet..."}
{"role":"user","content":"My name is Zhang San"}
{"role":"assistant","content":"Got it, Zhang San. Nice to meet you!"}
Why JSONL?
- Simple: one JSON object per line, easy to read/write
- Append-friendly: append new messages without rewriting the whole file
- Readable: view directly with a text editor
- Fault-tolerant: a corrupted line doesn’t break the whole file
“Memory” Implementation (Business-Layer Example)
Below is a simple example that stores conversation history in JSONL files. This is just one possible approach; you can use databases, Redis, and other storage options based on your needs.
1. Create a Store
sessionDir := "./data/sessions"
store, err := mem.NewStore(sessionDir)
if err != nil {
log.Fatal(err)
}
2. Get or Create a Session
sessionID := "083d16da-6b13-4fe6-afb0-c45d8f490ce1"
session, err := store.GetOrCreate(sessionID)
if err != nil {
log.Fatal(err)
}
3. Append a User Message
userMsg := schema.UserMessage("hello")
if err := session.Append(userMsg); err != nil {
log.Fatal(err)
}
4. Get History and Call the Agent
history := session.GetMessages()
events := runner.Run(ctx, history)
content := collectAssistantFromEvents(events)
5. Append an Assistant Message
assistantMsg := schema.AssistantMessage(content, nil)
if err := session.Append(assistantMsg); err != nil {
log.Fatal(err)
}
Key snippet (note: this is a simplified excerpt and not directly runnable; see cmd/ch03/main.go):
// Create or resume a session
session, err := store.GetOrCreate(sessionID)
if err != nil {
log.Fatal(err)
}
// User input
userMsg := schema.UserMessage(line)
if err := session.Append(userMsg); err != nil {
log.Fatal(err)
}
// Call the Agent
history := session.GetMessages()
events := runner.Run(ctx, history)
content := collectAssistantFromEvents(events)
// Save assistant reply
assistantMsg := schema.AssistantMessage(content, nil)
if err := session.Append(assistantMsg); err != nil {
log.Fatal(err)
}
Session vs Agent: How Business Layer and Framework Layer Work Together
Key takeaways:
- Session is a business-layer concept: implemented/managed by your code, responsible for storing/loading history
- Agent (Runner) is a framework-layer concept: provided by Eino, responsible for processing messages and producing replies
- Integration point: the business layer calls
session.GetMessages()to obtain the message list, and passes it torunner.Run(ctx, history)
Layering:
┌─────────────────────────────────────────────────────────────┐
│ Business layer (your code) │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Session │───→│ GetMessages() │───→│ runner.Run() │ │
│ │ (storage) │ │ (message list)│ │ (framework) │ │
│ └─────────────┘ └──────────────┘ └───────────────┘ │
│ ↑ │ │
│ │ ↓ │
│ ┌─────────────┐ ┌───────────────┐ │
│ │ Append() │←─────────────────────│ assistant reply│ │
│ │ (persist) │ └───────────────┘ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ Framework layer (Eino) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ adk.Runner: accepts message list, calls ChatModel, │ │
│ │ and returns replies │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Flow:
┌─────────────────────────────────────────┐
│ user input │
└─────────────────────────────────────────┘
↓
┌──────────────────────┐
│ session.Append() │
│ persist user message │
└──────────────────────┘
↓
┌──────────────────────┐
│ session.GetMessages()│
│ get full history │
└──────────────────────┘
↓
┌──────────────────────┐
│ runner.Run(history) │
│ agent processes │
└──────────────────────┘
↓
┌──────────────────────┐
│ collect reply │
└──────────────────────┘
↓
┌──────────────────────┐
│ session.Append() │
│ persist assistant │
└──────────────────────┘
Summary
Framework layer vs business layer:
- Eino framework: provides base abstractions like
adk.Runnerandschema.Message; it does not care how messages are stored - Business layer (this chapter’s sample): Memory/Session/Store are application-level concepts managing storage of chat history
Business-layer concepts:
- Memory: persistent storage for history, supports cross-process recovery
- Session: one complete conversation with ID, timestamps, message list
- Store: manages multiple sessions (create/get/list/delete)
- JSONL: a simple file format that is easy to append and inspect
How the two layers interact:
- business layer stores messages and calls
session.GetMessages()to get the list - pass the list to
runner.Run(ctx, history)for processing - collect replies from the framework and persist them back via the business layer
Tip: this chapter’s implementation is one simple example. In real projects, you can use databases, Redis, cloud storage, and implement additional capabilities like expiration cleanup, search, sharing, etc.
Further Thoughts: Choosing a Storage Solution
The JSONL approach works for simple single-machine apps. In production you may need other options:
Other storage implementations:
- databases (MySQL, PostgreSQL, MongoDB)
- Redis (distributed support)
- cloud storage (S3, OSS)
Advanced features:
- session expiration cleanup
- session search
- session export/import
- session sharing