Chapter 3: Memory and Session (Persistent Conversations)

Goal of this chapter: persist conversation history and support session recovery across processes.

⚠️ Important Note: Business-Layer Concepts vs Framework Concepts

The Memory, Session, and Store introduced in this chapter are business-layer concepts, not core Eino framework components.

  • Eino framework layer: provides base abstractions like adk.Runner, adk.NewTypedRunner[M], schema.AgenticMessage, etc. The framework itself does not concern itself with how conversation history is stored
  • Business layer: Memory/Session/Store are business logic designed by this example project to implement persistent conversations, interacting with the Eino framework by assembling inputs for adk.Runner

In other words, the Eino framework is only responsible for “how to process messages”, while “how to store messages” is entirely decided by the business layer. The implementation in this chapter is just a simple reference example — you can choose a completely different storage solution (database, Redis, cloud storage, etc.) based on your business needs.

Code Location

Prerequisites

Same as Chapter 1: you need a configured and available ChatModel (OpenAI or Ark).

Running

In the examples/quickstart/chatwitheino directory:

# 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> Hi, my name is Zhang San
[assistant] Hi Zhang San! Nice to meet you...
you> What's 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 conversation, but there’s a problem: conversation history only exists in memory.

Limitations of in-memory storage:

  • Conversation history is lost when the process exits
  • Cannot resume sessions across devices or processes
  • Cannot implement session management (listing, deletion, search, etc.)

Memory’s role:

  • Memory is persistent storage for conversation history: saving conversations to disk or a database
  • Memory supports Session management: each Session represents a complete conversation
  • Memory is decoupled from Agent: the Agent doesn’t care about storage details, it only cares about the message list

Simple analogy:

  • In-memory storage = “scratch paper” (gone when the process exits)
  • Memory = “notebook” (permanently saved, accessible anytime)

Key Concepts

Reminder: the Session, Store, and other concepts below are all business-layer implementations for managing conversation history storage. The Eino framework itself does not provide these components — the business layer is responsible for managing the message list, then passing messages to adk.Runner for processing.

Session (Business-Layer Concept)

Session represents a complete conversation:

type Session struct {
    ID        string
    CreatedAt time.Time

    messages []M  // Conversation history; in this example M defaults to *schema.AgenticMessage
    // ...
}

Core methods:

  • Append(msg): appends a message to the session and persists it
  • GetMessages(): retrieves all messages
  • Title(): generates a session 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 Session
  • List(): list all Sessions
  • Delete(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","message_kind":"agentic"}
{"role":"user","content_blocks":[{"type":"user_input_text","user_input_text":{"text":"Hello, who am I?"}}]}
{"role":"assistant","content_blocks":[{"type":"assistant_gen_text","assistant_gen_text":{"text":"Hello! I don't know who you are yet..."}}]}
{"role":"user","content_blocks":[{"type":"user_input_text","user_input_text":{"text":"My name is Zhang San"}}]}
{"role":"assistant","content_blocks":[{"type":"assistant_gen_text","assistant_gen_text":{"text":"OK, Zhang San, nice to meet you!"}}]}

Sessions are saved by default in ./data/sessions_agentic; to use a different directory, set SESSION_DIR_AGENTIC.

Why JSONL?

  • Simple: one JSON object per line, easy to read and write
  • Extensible: new messages can be appended without rewriting the entire file
  • Readable: can be viewed directly with a text editor
  • Fault-tolerant: a corrupted line doesn’t affect other lines

Memory Implementation (Business-Layer Example)

Below is a simple business-layer implementation example using JSONL files for conversation history storage. This is just one of many possible implementations — you can choose database, Redis, or other storage solutions based on your actual needs.

1. Create the Store

sessionDir := "./data/sessions_agentic"
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 User Message

userMsg := msgops.NewUser[M]("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, msgops.NormalizeMessagesForModelInput(history))
result, err := helpers.PrintAndCollect[M](events, helpers.PrintOptions{})
if err != nil {
    log.Fatal(err)
}

5. Append Assistant Message

assistantMsg := msgops.NewAssistant[M](result.AssistantText, nil)
if err := session.Append(assistantMsg); err != nil {
    log.Fatal(err)
}

Key code snippet (Note: this is a simplified snippet that cannot run directly; see cmd/ch03/main.go for the full code):

store, err := mem.NewStore[M](msgops.DefaultSessionDir(msgops.KindOf[M]()))
if err != nil {
    log.Fatal(err)
}

// Create or resume a Session
session, err := store.GetOrCreate(sessionID)
if err != nil {
    log.Fatal(err)
}

// User input
userMsg := msgops.NewUser[M](line)
if err := session.Append(userMsg); err != nil {
    log.Fatal(err)
}

// Call the Agent
history := session.GetMessages()
events := runner.Run(ctx, msgops.NormalizeMessagesForModelInput(history))
result, err := helpers.PrintAndCollect[M](events, helpers.PrintOptions{})
if err != nil {
    log.Fatal(err)
}

// Save assistant reply
assistantMsg := msgops.NewAssistant[M](result.AssistantText, nil)
if err := session.Append(assistantMsg); err != nil {
    log.Fatal(err)
}

Session and Agent Relationship: Business Layer and Framework Layer Collaboration

Key understanding:

  • Session is a business-layer concept: implemented and managed by business code, responsible for storing and loading conversation history
  • Agent (Runner) is a framework-layer concept: provided by the Eino framework, responsible for processing messages and generating replies
  • Their interaction point: the business layer gets the message list via session.GetMessages(), then generates model input via msgops.NormalizeMessagesForModelInput(history), and finally passes it to runner.Run(ctx, messages) for processing

Architecture layers:

┌─────────────────────────────────────────────────────────────┐
│                   Business Layer (your code)                  │
│  ┌─────────────┐    ┌──────────────┐    ┌───────────────┐  │
│  │   Session   │───→│ GetMessages() │───→│ runner.Run()  │  │
│  │  (storage)  │    │ (msg list)    │    │ (framework)   │  │
│  └─────────────┘    └──────────────┘    └───────────────┘  │
│         ↑                                      │            │
│         │                                      ↓            │
│  ┌─────────────┐                      ┌───────────────┐    │
│  │   Append()  │←─────────────────────│ Assistant reply│    │
│  │ (save msg)  │                      └───────────────┘    │
│  └─────────────┘                                           │
└─────────────────────────────────────────────────────────────┘
                              │
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                 Framework Layer (Eino framework)              │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ adk.Runner: receives message list, calls ChatModel,   │  │
│  │ returns reply                                         │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Flow diagram:

┌─────────────────────────────────────────┐
│  User input                              │
└─────────────────────────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  session.Append()    │
        │  Save user message   │
        └──────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  session.GetMessages()│
        │  Get full history    │
        └──────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  runner.Run(history) │
        │  Agent processes msgs│
        └──────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  Collect assistant   │
        │  reply               │
        └──────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  session.Append()    │
        │  Save assistant msg  │
        └──────────────────────┘

Chapter Summary

Framework Layer vs Business Layer:

  • Eino framework layer: provides base abstractions like adk.Runner, typed runner, schema.AgenticMessage, etc., and does not concern itself with how messages are stored
  • Business layer (this chapter’s implementation): Memory/Session/Store are business-layer concepts for managing conversation history storage

Business-layer concepts:

  • Memory: persistent storage for conversation history, supporting cross-process recovery
  • Session: a complete conversation, containing ID, creation time, and message list
  • Store: manages storage for multiple Sessions, supporting create, get, list, and delete
  • JSONL format: a simple file format, easy to read/write and extend

Business layer and framework layer interaction:

  • The business layer stores messages and retrieves the message list via session.GetMessages()
  • After normalizing the message list for model input, it passes it to the framework layer’s runner.Run(ctx, messages) for processing
  • The framework layer’s reply is collected and saved back to storage by the business layer

💡 Tip: The implementation in this chapter is just one simple example among many storage solutions. In real projects, you can choose databases, Redis, cloud storage, etc. based on your business needs, and even implement more advanced features like session expiration cleanup, search, sharing, etc.

Extended Thinking: Choosing a Business-Layer Storage Solution

The JSONL file storage approach in this chapter is suitable for simple single-machine applications. In real business scenarios, you may want to consider other storage solutions:

Alternative storage implementations:

  • Database storage (MySQL, PostgreSQL, MongoDB)
  • Redis storage (supports distributed setups)
  • Cloud storage (S3, OSS)

Advanced features:

  • Session expiration cleanup
  • Session search
  • Session export/import
  • Session sharing