Persistence & history

The default Drizzle store writes conversations and messages to Postgres. It is constructed per request bound to the verified user.

Drizzle schema

  • chat_conversations: id, user id, title, metadata, created and updated times.
  • chat_messages: id, conversation id, role, canonical parts, text projection, model, and created time.

messageCount is not stored on the conversation row. The Drizzle listConversations query computes it from messages for the history sidebar.

Turn persistence

The handler persists the latest user message, streams the response, then calls saveTurn for the completed assistant turn. Store implementations must verify ownership and deduplicate stable message ids.

code
await store.saveTurn({
  conversationId,
  messages: finalMessages,
  model,
  usage,
});

The complete parts array is authoritative. The text column is a denormalized projection for previews/search.

History routes

  • GET /history calls listConversations.
  • GET /history/:conversationId calls listMessages, returns chronological messages, and re-signs stored attachments when storage is configured.

The Drizzle store supports limit and before pagination and caps pages at 100.

Warning

The current hosted store ignores ListMessagesOptions and fetches the complete conversation. Do not rely on before pagination when using that adapter until hosted pagination is implemented.

Model context versus stored history

maxHistoryMessages (default 30) and maxMessageChars (default 4000) affect what is sent to the model. They do not delete stored messages or hide history from the UI.

code
export const { GET, POST, DELETE } = createChatHandler({
  getUserId: getChatUserId,
  model,
  store: createDrizzleChatStore(),
  maxHistoryMessages: 40,
  maxMessageChars: 6000,
});

Set maxMessageChars: 0 to disable the character cap. Use summarizeHistory when dropped early messages must remain available as condensed, non-authoritative background.

See ChatStore for the full contract.