# How to Serialize and Persist Conversation Context in π‑AI: A Complete Guide

> Learn to serialize and persist conversation context in π-AI using JSON-Lines with serializeConversation() and SessionManager. Manage your chat history effectively.

- Repository: [Mario Zechner/pi-mono](https://github.com/badlogic/pi-mono)
- Tags: how-to-guide
- Published: 2026-02-16

---

**π‑AI stores conversation history as a session tree on disk using JSON‑Lines (`.jsonl`) files, which you can generate via `serializeConversation()` and manage automatically through the `SessionManager` class.**

The `badlogic/pi-mono` repository provides a robust pipeline for turning in‑memory chat interactions into stable, persisted representations. Whether you need to create a plain‑text transcript for summarization or resume a multi‑turn coding session days later, π‑AI’s serialization layer handles normalization, compaction, and atomic disk writes without manual file handling.

## Understanding the Serialization Pipeline

Before data reaches the disk, π‑AI transforms rich UI messages into a normalized format suitable for both LLM APIs and long‑term storage.

### Step 1 – Normalize Messages with `convertToLlm`

UI components and extensions often produce `Message` objects containing special types such as `artifact` or tool results. The `convertToLlm()` function, re‑exported from [`packages/coding-agent/src/index.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/index.ts), flattens these into the plain message shape defined in [`packages/ai/src/types.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/types.ts).

This normalization ensures that downstream serialization logic receives a consistent `Message[]` array regardless of whether the input came from a chat widget, a coding agent, or a batch import.

### Step 2 – Generate Plain‑Text Transcripts with `serializeConversation`

Once normalized, the `serializeConversation(messages)` function—implemented in [`packages/coding-agent/src/core/compaction/utils.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts)—walks the array and creates a deterministic plain‑text transcript. The output uses a structured format such as `[User]: …`, `[Assistant]: …`, and dedicated blocks for tool calls or reasoning traces.

Because this transcript is **not** interpreted as a continuing chat, you can safely feed it to a summarization LLM or store it as an audit log without triggering the model’s auto‑continuation behavior.

## Persisting Conversations with `SessionManager`

While serialization prepares the data, the `SessionManager` class—defined in [`packages/coding-agent/src/core/session-manager.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts)—handles atomic persistence and retrieval.

### Automatic Persistence to JSONL Files

When you instantiate `SessionManager` with `persist: true`, the manager creates a timestamped `.jsonl` file in the specified `sessionDir`. Every call to `appendMessage()` triggers the following chain:

1.  A `SessionMessageEntry` object is created from the incoming message.
2.  `_appendEntry()` adds the entry to an in‑memory buffer and updates the session tree index.
3.  `_persist(entry)` atomically appends the JSON representation to the disk file.

The system flushes to disk immediately once the first assistant message appears, ensuring that user queries are never lost even if the process crashes during generation.

### Resuming Sessions from Disk

To resume a conversation, instantiate `SessionManager` with the `sessionFile` parameter pointing to an existing `.jsonl` file. The constructor calls `setSessionFile()`, which rebuilds the tree index by streaming the file line‑by‑line.

Once loaded, `buildSessionContext()` walks from the root to the current leaf and returns the effective `Message[]` list, including any serialized transcripts you previously stored via `serializeConversation`. This allows you to maintain long‑running coding sessions across restarts or share conversation states between team members.

## Practical Implementation Examples

### Serializing for Summarization

Use `convertToLlm` and `serializeConversation` to create a safe, non‑continuing transcript for external processing:

```typescript
import {
  convertToLlm,
  serializeConversation,
} from "@mariozechner/pi-coding-agent";

// Normalize rich UI messages to plain LLM format
const llmMessages = convertToLlm(allMessages);

// Generate deterministic plain-text transcript
const conversationText = serializeConversation(llmMessages);

// Feed to summarization model without continuation side effects
await aiProvider.stream({
  model: "gpt-4o-mini",
  system: "Summarize the following coding session.",
  prompt: conversationText,
});

```

### Creating a Persistent Session

Enable automatic disk persistence when initializing the session manager:

```typescript
import { SessionManager } from "@mariozechner/pi-coding-agent";

const session = SessionManager.create({
  cwd: process.cwd(),
  sessionDir: "./.pi-sessions",
  persist: true,  // Enables JSONL file creation
});

// This writes to disk immediately after the first assistant response
session.appendMessage({
  role: "user",
  content: "Refactor the authentication middleware.",
});

```

### Loading an Existing Session

Resume work from a previous session file:

```typescript
const resumed = SessionManager.create({
  cwd: process.cwd(),
  sessionDir: "./.pi-sessions",
  sessionFile: "/absolute/path/to/2024-10-31_session.jsonl",
  persist: true,
});

// Reconstruct the conversation context
const messages = resumed.buildSessionContext();

```

## Key Source Files and Architecture

| File | Purpose | Location |
|------|---------|----------|
| [`packages/coding-agent/src/core/compaction/utils.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts) | Contains `serializeConversation()` for generating plain‑text transcripts. | [View on GitHub](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts) |
| [`packages/coding-agent/src/core/session-manager.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts) | Implements `SessionManager` with `_persist()`, `appendMessage()`, and `buildSessionContext()`. | [View on GitHub](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts) |
| [`packages/coding-agent/src/index.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/index.ts) | Public API entry point re‑exporting `convertToLlm`, `serializeConversation`, and `SessionManager`. | [View on GitHub](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/index.ts) |
| [`packages/ai/src/types.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/types.ts) | Defines the `Message` interface used throughout the serialization pipeline. | [View on GitHub](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/types.ts) |

## Summary

- **Normalization first:** Always run `convertToLlm()` to flatten rich UI messages into the standard `Message` format before serialization.
- **Safe transcripts:** Use `serializeConversation()` from [`packages/coding-agent/src/core/compaction/utils.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts) to create deterministic plain‑text logs that won't trigger LLM continuation.
- **Atomic persistence:** Instantiate `SessionManager` with `persist: true` to enable automatic JSONL file writes; never manually handle file I/O.
- **Session resumption:** Load existing conversations by passing a `sessionFile` path to `SessionManager.create()`, then call `buildSessionContext()` to reconstruct the message history.

## Frequently Asked Questions

### What file format does π‑AI use to store conversation history?

π‑AI persists sessions as **JSON‑Lines (`.jsonl`)** files, where each line represents a `SessionMessageEntry` object. This format supports atomic appends and allows the `SessionManager` to rebuild the conversation tree by streaming the file line‑by‑line during session resumption.

### How does `serializeConversation` differ from standard JSON serialization?

Unlike `JSON.stringify()`, which produces machine‑readable JSON that an LLM might interpret as a prompt continuation, `serializeConversation()` generates a **deterministic plain‑text transcript** using human‑readable markers like `[User]:` and `[Assistant]:`. This isolation prevents the summarization model from treating the log as an active chat session.

### Can I resume a conversation from a specific point in the session tree?

Yes. The `SessionManager` maintains a full **session tree** structure, allowing you to navigate to any historical node. When you call `buildSessionContext()`, the manager walks from the root to the currently selected leaf, returning only the messages along that path. You can programmatically switch leaves to resume from earlier branches.

### Is conversation persistence enabled by default in `SessionManager`?

No. Persistence is **opt‑in**. You must explicitly set `persist: true` in the `SessionManager.create()` options. When enabled, the manager creates a timestamped `.jsonl` file in your specified `sessionDir` and begins atomic writes immediately after the first assistant message appears.