# How 5ire Manages Conversation History per Chat Session: SQLite and Zustand Implementation

> Learn how 5ire manages conversation history with SQLite for persistence and Zustand for real-time in-memory state. Discover efficient loading and synchronization.

- Repository: [Ironben/5ire](https://github.com/nanbingxyz/5ire)
- Tags: internals
- Published: 2026-03-07

---

**5ire maintains conversation history using a dual-layer architecture where SQLite provides durable persistence and Zustand manages reactive in-memory state, loading historic messages on demand and synchronizing every create, update, or delete operation across both stores in real time.**

Managing conversation history efficiently is critical for AI chat applications that require both durability and responsiveness. In the open-source **5ire** repository, the system implements a robust pipeline that persists every turn to SQLite while maintaining a reactive Zustand store for the active chat session. This architecture ensures fast UI updates, reliable data durability, and precise control over context windows sent to AI providers.

## SQLite Schema for Persistent Storage

All conversation history is durably stored in a SQLite database managed by the Electron main process. The schema is defined in [`src/main/sqlite.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/sqlite.ts), which creates a `messages` table—linked to the `chats` table via foreign key constraints—that stores complete turn data including token usage and model parameters:

```ts
// src/main/sqlite.ts (lines 65-82)
CREATE TABLE IF NOT EXISTS "messages" (
  "id" TEXT PRIMARY KEY,
  "chatId" TEXT NOT NULL,
  "prompt" TEXT,
  "reply" TEXT,
  "model" TEXT,
  "temperature" REAL,
  "inputTokens" INTEGER,
  "outputTokens" INTEGER,
  "reasoning" TEXT,
  "structuredPrompts" TEXT,
  "createdAt" INTEGER NOT NULL,
  CONSTRAINT "fk_messages_chats" FOREIGN KEY ("chatId")
    REFERENCES "chats" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

```

The `ON DELETE CASCADE` constraint ensures that deleting a chat automatically purges its associated conversation history, preventing orphaned records.

## Zustand Store for In-Memory State Management

While SQLite handles persistence, the renderer process maintains a reactive **Zustand** store defined in [`src/stores/useChatStore.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/stores/useChatStore.ts). The store holds a `messages` array (typed as `IChatMessage` from [`src/main/database/types.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/database/types.ts)) representing the currently loaded conversation history for the active chat session:

```ts
// src/stores/useChatStore.ts (lines 34-38)
export interface IChatStore {
  // ... other fields
  messages: IChatMessage[];
  // ...
}

```

### Loading Historic Messages

When a user opens a chat, the system loads conversation history from SQLite into the Zustand store via the `fetchMessages` action. This function executes parameterized SQL queries with optional keyword filtering and pagination support:

```ts
// src/stores/useChatStore.ts (lines 693-706)
fetchMessages: async ({
  chatId,
  limit = 100,
  offset = 0,
  keyword = "",
}) => {
  if (chatId === TEMP_CHAT_ID) {
    set({ messages: [] });
    return [];
  }
  let sql = `SELECT messages.*, bookmarks.id bookmarkId
    FROM messages
    LEFT JOIN bookmarks ON bookmarks.msgId = messages.id
    WHERE messages.chatId = ?`;
  let params = [chatId, limit, offset];
  if (keyword && keyword.trim() !== "") {
    sql += ` AND (messages.prompt LIKE ? COLLATE NOCASE OR messages.reply LIKE ? COLLATE NOCASE)`;
    params = [chatId, `%${keyword.trim()}%`, `%${keyword.trim()}%`, limit, offset];
  }
  sql += ` ORDER BY messages.createdAt ASC LIMIT ? OFFSET ?`;
  const messages = (await window.electron.db.all(sql, params)) as IChatMessage[];
  set({ messages });
  return messages;
},

```

The function handles temporary chats (identified by `TEMP_CHAT_ID`) by returning an empty array, while standard chats query the database and update the store atomically.

### Synchronizing Modifications

All mutations to conversation history propagate to both storage layers simultaneously using Immer's `produce` for immutable updates:

- **`createMessage`** – Inserts a row into SQLite via the main process, then appends the new message object to the `messages` array in the Zustand store.
- **`updateMessage`** – Executes an UPDATE statement against the database and replaces the corresponding entry in the in-memory array.
- **`deleteMessage`** – Removes the row from SQLite and splices the entry out of `state.messages`.

These operations ensure the UI remains synchronized with the underlying database without requiring full reloads of the conversation history.

## Context Window Construction for AI Inference

Before sending requests to AI providers, 5ire trims the full conversation history to a relevant context window. The [`src/renderer/ChatContext.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/renderer/ChatContext.ts) module provides `getCtxMessages`, which extracts only the most recent completed turns based on the chat's `maxCtxMessages` configuration:

```ts
// src/renderer/ChatContext.ts (lines 70-90)
const getCtxMessages = (msgId?: string) => {
  const chat = getActiveChat();
  const maxCtxMessages = isNumber(chat?.maxCtxMessages) ? chat?.maxCtxMessages : NUM_CTX_MESSAGES;
  if (maxCtxMessages > 0) {
    let messages = useChatStore.getState().messages || [];
    // Truncate at specific message ID when regenerating
    if (msgId) {
      const index = messages.findIndex((m) => m.id === msgId);
      if (index > -1) messages = messages.slice(0, index);
    }
    // Filter for fully-formed turns only
    messages = messages.filter((m) => m.prompt && m.reply);
    // Return the most recent N turns
    return messages.length > maxCtxMessages
      ? messages.slice(-maxCtxMessages)
      : messages;
  }
  return [];
};

```

This function filters out incomplete turns (where `prompt` or `reply` is missing) and supports truncation at specific message IDs for regeneration scenarios. The resulting array represents the **conversation history** actually transmitted to the language model.

## Handling Temporary Chat Sessions

For unsaved or temporary chats, 5ire bypasses SQLite entirely until the user explicitly persists the session. When `chatId` equals `TEMP_CHAT_ID`, the system stores conversation state in `window.electron.store` rather than the database. Upon saving, `createChat` writes a new row to the `chats` table, after which `createMessage` begins persisting subsequent messages to SQLite, and the conversation history becomes durable.

## Summary

- **Dual-layer persistence**: 5ire stores conversation history in SQLite for durability and Zustand for reactive UI state management.
- **On-demand loading**: The `fetchMessages` function in [`src/stores/useChatStore.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/stores/useChatStore.ts) queries SQLite and hydrates the in-memory store when a chat becomes active.
- **Real-time synchronization**: Every creation, update, or deletion propagates immediately to both the SQLite database and the Zustand `messages` array using Immer's `produce`.
- **Context optimization**: `getCtxMessages` in [`src/renderer/ChatContext.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/renderer/ChatContext.ts) filters and slices conversation history to respect `maxCtxMessages` limits before AI inference.
- **Temporary session support**: Unsaved chats use `TEMP_CHAT_ID` and `window.electron.store`, transitioning to SQLite only upon explicit creation.

## Frequently Asked Questions

### How does 5ire handle large conversation histories?

Rather than loading unlimited history, the `fetchMessages` function implements pagination with `limit` and `offset` parameters (defaulting to 100 messages). Additionally, `getCtxMessages` enforces a `maxCtxMessages` cap when building the context window, ensuring only the most relevant recent turns are sent to the AI provider regardless of total storage size.

### What happens to conversation history when a user deletes a chat?

The SQLite schema defines a foreign key constraint with `ON DELETE CASCADE` linking the `messages` table to the `chats` table. Deleting a chat row automatically removes all associated message rows, ensuring complete cleanup of conversation history without orphan records.

### Can users search through past conversation history?

Yes. The `fetchMessages` function accepts an optional `keyword` parameter that performs case-insensitive SQL `LIKE` queries against both the `prompt` and `reply` columns using `COLLATE NOCASE`, allowing full-text search across stored conversation history.

### How does 5ire manage conversation history for unsaved chats?

Temporary chats identified by `TEMP_CHAT_ID` never write to SQLite. Instead, their state resides in `window.electron.store`. Conversation history for these sessions exists only in memory until the user explicitly saves the chat, at which point the system migrates to the standard SQLite-backed workflow.