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

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, 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:

// 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. The store holds a messages array (typed as IChatMessage from src/main/database/types.ts) representing the currently loaded conversation history for the active chat session:

// 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:

// 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 module provides getCtxMessages, which extracts only the most recent completed turns based on the chat's maxCtxMessages configuration:

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

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 →