# How Cherry Studio's Memory System Stores and Retrieves Conversation Context

> Learn how Cherry Studio's memory system stores and retrieves conversation context using a three-layer architecture for LLM fact extraction, SQLite storage, and hybrid search.

- Repository: [CherryHQ/cherry-studio](https://github.com/cherryhq/cherry-studio)
- Tags: internals
- Published: 2026-02-27

---

**Cherry Studio uses a three-layer architecture that extracts personal facts from conversations using LLM prompts, stores them in a local SQLite database with optional vector embeddings, and retrieves relevant context through hybrid similarity search.**

Cherry Studio is an open-source AI desktop application that implements a persistent memory system to maintain conversation context across sessions. The memory system stores and retrieves conversation context by extracting personal facts, deduplicating entries, and enabling vector-based similarity search. This article examines the implementation based on the `cherryhq/cherry-studio` source code.

## Architecture Overview

The memory implementation spans three distinct layers across the renderer and main processes:

| Layer | Responsibility | Core Implementation |
|------|----------------|----------------------|
| **1. Fact extraction** | The LLM is prompted to pull personal facts from the chat. The prompt is defined in [`memory-prompts.ts`](https://github.com/cherryhq/cherry-studio/blob/main/memory-prompts.ts) and the output is validated with Zod schemas (`FactRetrievalSchema`). | [`src/renderer/src/utils/memory-prompts.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/renderer/src/utils/memory-prompts.ts) |
| **2. Storage & indexing** | Extracted facts are turned into *memory items* and stored in a local SQLite database (`libsql`). Each item is hashed, optionally embedded with a vector model, and indexed for fast similarity search. | [`src/main/services/memory/MemoryService.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/main/services/memory/MemoryService.ts) (database schema, embedding generation, deduplication, vector index) |
| **3. Retrieval** | When a new user query arrives, the renderer asks the main process for a similarity search. The search can be a plain keyword match or a hybrid vector‑plus‑filter query, returning the most relevant memories. | [`src/renderer/src/services/MemoryService.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/renderer/src/services/MemoryService.ts) (search API) and [`src/main/services/memory/MemoryService.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/main/services/memory/MemoryService.ts) (hybridSearch implementation) |

## Fact Extraction Layer

### LLM Prompting and Schema Validation

The system extracts personal facts by sending the conversation transcript to an LLM with a specialized prompt. In [`src/renderer/src/utils/memory-prompts.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/renderer/src/utils/memory-prompts.ts), the `factExtractionPrompt` instructs the model to identify user-specific information such as preferences, personal details, or prior context.

The LLM response must conform to `FactRetrievalSchema`, a Zod schema that validates the JSON structure. This ensures the output contains a list of fact strings before they proceed to storage.

```typescript
import { FactRetrievalSchema } from '@renderer/utils/memory-prompts'

async function extractFacts(transcript: string) {
  const llmResponse = await llm.invoke({
    prompt: factExtractionPrompt,
    variables: { transcript }
  })
  const parsed = FactRetrievalSchema.safeParse(JSON.parse(llmResponse))
  if (!parsed.success) throw new Error('Invalid fact format')
  return parsed.data.facts               // => string[]
}

```

## Storage and Indexing Layer

### Database Schema and Initialization

The main process handles persistence through [`src/main/services/memory/MemoryService.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/main/services/memory/MemoryService.ts). During initialization, the service creates SQLite tables for `memories` and `memory_history`, along with vector indexes for similarity search. The database uses `libsql` (Turso's SQLite fork) to support vector extensions.

### Deduplication and Hashing

Before inserting a new fact, the system computes a SHA-256 hash of the trimmed fact string. If an existing non-deleted record shares the same hash, the insertion is skipped. This prevents duplicate entries from cluttering the memory store.

```typescript
// Simplified deduplication logic from MemoryService.ts
const hash = crypto.createHash('sha256').update(fact.trim()).digest('hex')
const existing = await db.query('SELECT id FROM memories WHERE hash = ? AND deleted_at IS NULL', [hash])
if (existing.length > 0) return // Skip duplicate

```

### Vector Embeddings and Similarity Guard

If an embedding model is configured, `generateEmbedding()` creates a dense vector (default dimension 1536) for each fact. Before final insertion, a **similarity guard** runs a hybrid vector search against existing memories. If any existing memory scores ≥ 0.85 similarity, the new fact is rejected as redundant.

```typescript
// From src/main/services/memory/MemoryService.ts
async function add(fact: string, options: MemoryOptions) {
  const embedding = await generateEmbedding(fact)
  const similar = await hybridSearch(fact, embedding, { threshold: 0.85 })
  if (similar.length > 0) return // Too similar to existing memory
  
  const id = crypto.randomUUID()
  await db.insert('memories', { id, content: fact, embedding, hash, ... })
  await db.insert('memory_history', { memory_id: id, action: 'ADD' })
}

```

## Retrieval Layer

### Hybrid Search Implementation

When the UI needs context for a new user message, the renderer calls `MemoryService.search(query, options)`. This forwards the request via IPC to the main process's `hybridSearch` implementation.

The search combines:
- **Vector similarity**: Cosine similarity against stored embeddings (if available)
- **Metadata filtering**: By `userId`, `agentId`, or other tags
- **Keyword matching**: Full-text search as a fallback or boost

Results are ranked by composite score and returned to the renderer, where they are injected into the next LLM prompt.

```typescript
// Renderer-side search usage
async function getRelevantMemories(message: string, userId: string) {
  const memSvc = MemoryService.getInstance()
  memSvc.setCurrentUser(userId)

  const result = await memSvc.search(message, {
    userId,
    limit: 5,
    threshold: 0.5
  })

  return result.results.map(m => m.memory)   // array of matching memory strings
}

```

## Code Examples

### Extracting Facts from a Chat Transcript

```typescript
import { FactRetrievalSchema } from '@renderer/utils/memory-prompts'

async function extractFacts(transcript: string) {
  const llmResponse = await llm.invoke({
    prompt: factExtractionPrompt,
    variables: { transcript }
  })
  const parsed = FactRetrievalSchema.safeParse(JSON.parse(llmResponse))
  if (!parsed.success) throw new Error('Invalid fact format')
  return parsed.data.facts               // => string[]
}

```

*Uses the prompt defined in [`memory-prompts.ts`](https://github.com/cherryhq/cherry-studio/blob/main/memory-prompts.ts) (lines 92-104).*

### Adding Extracted Facts to Memory

```typescript
import MemoryService from '@renderer/services/MemoryService'

async function storeFacts(facts: string[], userId: string) {
  const memSvc = MemoryService.getInstance()
  memSvc.setCurrentUser(userId)

  await memSvc.add(facts, {
    userId,
    agentId: 'assistant-1',
    runId: crypto.randomUUID()
  })
}

```

*Calls the renderer service ([`src/renderer/src/services/MemoryService.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/renderer/src/services/MemoryService.ts) lines 31-38).*

### Searching for Memories When Handling a New User Message

```typescript
async function getRelevantMemories(message: string, userId: string) {
  const memSvc = MemoryService.getInstance()
  memSvc.setCurrentUser(userId)

  const result = await memSvc.search(message, {
    userId,
    limit: 5,
    threshold: 0.5
  })

  return result.results.map(m => m.memory)   // array of matching memory strings
}

```

*Wraps the `search` method (renderer) which forwards to the main-process vector search.*

### Updating Memory Configuration

```typescript
import store from '@renderer/store'
import { setGlobalMemoryEnabled, updateMemoryConfig } from '@renderer/store/memory'

function changeEmbeddingModel(newModel) {
  store.dispatch(updateMemoryConfig({
    ...store.getState().memory.memoryConfig,
    embeddingModel: newModel
  }))
  // The MemoryService automatically pushes the new config to the main process
}

```

*Triggers `updateConfig()` in the renderer service (lines 98-110).*

## Key Implementation Files

| File | Role |
|------|------|
| [`src/renderer/src/utils/memory-prompts.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/renderer/src/utils/memory-prompts.ts) | Defines LLM prompts and Zod schemas for fact extraction and memory-update instructions. |
| [`src/renderer/src/services/MemoryService.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/renderer/src/services/MemoryService.ts) | Front-end façade; handles IPC calls for list/add/search/delete and keeps the current user context. |
| [`src/main/services/memory/MemoryService.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/main/services/memory/MemoryService.ts) | Core backend; creates the SQLite DB, stores memories, generates embeddings, performs deduplication and hybrid vector search. |
| [`src/main/services/memory/queries.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/main/services/memory/queries.ts) (referenced) | SQL statements for table creation, indexes, and CRUD operations. |
| [`src/renderer/src/store/memory.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/renderer/src/store/memory.ts) | Redux slice that holds the global memory configuration and enables/disables the feature. |
| [`src/renderer/src/hooks/useAppInit.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/renderer/src/hooks/useAppInit.ts) | Initializes the memory service on app startup and reacts to config changes. |

These files together implement a persistent, vector-enabled memory store that extracts personal facts, de-duplicates them, and makes them searchable for context-aware conversations.

## Summary

- **Three-layer architecture**: Fact extraction via LLM prompts, SQLite storage with vector indexing, and hybrid similarity retrieval.
- **Deduplication**: SHA-256 hashing prevents identical fact storage, while vector similarity (≥0.85 threshold) blocks near-duplicate entries.
- **Vector support**: Optional embedding generation (default 1536 dimensions) enables semantic similarity search alongside metadata filtering.
- **IPC communication**: The renderer process ([`MemoryService.ts`](https://github.com/cherryhq/cherry-studio/blob/main/MemoryService.ts)) delegates database operations to the main process via IPC, maintaining separation between UI and data layers.
- **Audit trail**: Every memory operation creates a history entry (`memory_history` table) tracking additions, updates, and deletions.

## Frequently Asked Questions

### How does Cherry Studio prevent duplicate memories from being stored?

Cherry Studio implements a two-stage deduplication process in [`src/main/services/memory/MemoryService.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/main/services/memory/MemoryService.ts). First, it computes a SHA-256 hash of the trimmed fact string and checks for existing non-deleted records with the same hash. Second, before insertion, it runs a hybrid vector search with a 0.85 similarity threshold; if any existing memory is semantically similar enough, the new fact is rejected as redundant.

### What database does Cherry Studio use for memory storage?

The memory system uses **libsql** (Turso's SQLite fork) as its storage engine, implemented in [`src/main/services/memory/MemoryService.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/main/services/memory/MemoryService.ts). This provides native SQLite compatibility while supporting vector extensions for embedding storage and similarity search. The database schema includes `memories` and `memory_history` tables with appropriate indexes for fast retrieval.

### Can Cherry Studio work without an embedding model?

Yes, the memory system functions in a **keyword-only mode** when no embedding model is configured. In [`src/main/services/memory/MemoryService.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/main/services/memory/MemoryService.ts), the `hybridSearch` method adapts to available resources: if embeddings exist, it performs vector similarity search; otherwise, it falls back to metadata filtering and full-text search. However, semantic similarity detection for deduplication requires embeddings to achieve the 0.85 similarity threshold check.

### How is the memory configuration updated at runtime?

Configuration updates flow through the Redux store in [`src/renderer/src/store/memory.ts`](https://github.com/cherryhq/cherry-studio/blob/main/src/renderer/src/store/memory.ts) and propagate via the renderer's `MemoryService`. When `updateMemoryConfig()` is dispatched, the renderer service's `updateConfig()` method (lines 98-110) pushes the new settings—including embedding model changes—to the main process via IPC. The main process then reinitializes the embedding client and database connection with the updated parameters without requiring an application restart.