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 themessagesarray 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 ofstate.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
fetchMessagesfunction insrc/stores/useChatStore.tsqueries 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
messagesarray using Immer'sproduce. - Context optimization:
getCtxMessagesinsrc/renderer/ChatContext.tsfilters and slices conversation history to respectmaxCtxMessageslimits before AI inference. - Temporary session support: Unsaved chats use
TEMP_CHAT_IDandwindow.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →