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

π‑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, flattens these into the plain message shape defined in 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—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—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:

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:

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:

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 Contains serializeConversation() for generating plain‑text transcripts. View on GitHub
packages/coding-agent/src/core/session-manager.ts Implements SessionManager with _persist(), appendMessage(), and buildSessionContext(). View on GitHub
packages/coding-agent/src/index.ts Public API entry point re‑exporting convertToLlm, serializeConversation, and SessionManager. View on GitHub
packages/ai/src/types.ts Defines the Message interface used throughout the serialization pipeline. View on GitHub

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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →