Skip to main content

Persistence

By default everything is kept in memory and lost on restart. In production, pass your own storage to createAgent. All three options are independent and optional.

OptionHoldsDefault
checkpointerChat messagesIn-memory
threadStoreHistory list per user (id, title, updatedAt)In-memory
vectorStoreKnowledge embeddingsIn-memory
createAgent({ …, checkpointer, threadStore, vectorStore });

Startup logs a warning when checkpointer is missing, or when it is set without threadStore.

Messages: checkpointer​

Any LangGraph BaseCheckpointSaver. Postgres example:

import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";

const checkpointer = PostgresSaver.fromConnString(process.env.DATABASE_URL!);
await checkpointer.setup(); // creates the tables; run once at startup

When a user deletes a thread, sideagent calls checkpointer.deleteThread(threadId) if the saver has it.

History list: threadStore​

Implement ThreadStore. Key every row by (userId, threadId).

import type { ThreadStore } from "sideagent/server";

const threadStore: ThreadStore = {
// newest first
list: async (userId) => /* SELECT id, title, updated_at AS "updatedAt" … ORDER BY updated_at DESC */,
has: async (userId, threadId) => /* SELECT 1 … WHERE user_id = $1 AND id = $2 */,
// create with `title` if new; otherwise only bump updatedAt. Never overwrite the title.
touch: async (userId, threadId, title) => /* INSERT … ON CONFLICT (user_id, id) DO UPDATE SET updated_at = now() */,
delete: async (userId, threadId) => /* DELETE … WHERE user_id = $1 AND id = $2 */,
};
  • On a LangGraph BaseStore (Postgres, Redis, …) you can skip this and use new LangGraphThreadStore(store) from sideagent/server. It keeps the same 200-thread cap as MemoryThreadStore.
  • has is the ownership check. A thread that is not in the user's own index returns 404, even if the id is guessed.
  • updatedAt is a number of milliseconds since epoch.
  • The client shows at most 50 threads. MemoryThreadStore keeps at most 200 per user; your store has no such cap unless you add one.

Knowledge: vectorStore​

Any LangChain vector store: VectorStoreInterface from @langchain/core/vectorstores (pgvector, Vectorize, Qdrant, …). Sideagent embeds the chunks itself, so the store's own embeddings are not used. It calls:

  • addVectors(vectors, documents, { ids }) with stable ids <sourceId>#<n>. A refresh must overwrite rows with the same id (upsert).
  • delete({ ids }) for chunks a refreshed source no longer has.
  • similaritySearchVectorWithScore(vector, k). Each document carries metadata: { sourceId, title, url? }.
import { PGVectorStore } from "@langchain/community/vectorstores/pgvector";

const vectorStore = await PGVectorStore.initialize(embeddings, { postgresConnectionOptions, tableName: "sideagent_kb" });
createAgent({ …, vectorStore });
  • vector size is set by your embedding model. The column dimension must match it. Changing embeddings.model needs re-embedding, so clear the table first.
  • With a persistent store, the agent still re-embeds sources on every start. The store only avoids keeping them in process memory.