Skip to content

Context Items

Context items are reusable text snippets that can be attached to chats as system prompts. They support semantic search powered by pgvector embeddings, allowing users to find relevant context by meaning rather than keywords.

When a chat is created with a context_text value, the backend inserts it as a system message at position 0:

POST /api/sessions/{session_id}/chats
{
"model_name": "GPT-4",
"title": "Medical Q&A",
"context_text": "You are a medical assistant specializing in cardiology."
}

This results in the following message history:

PositionRoleContent
0systemYou are a medical assistant specializing in cardiology.
1userWhat causes atrial fibrillation?
2assistantAtrial fibrillation is caused by…

Every subsequent LLM call includes the system message, giving the model persistent instructions.

POST /api/context-items
{
"title": "Cardiology Expert",
"category": "medical",
"tags": ["cardiology", "expert"],
"content": "You are a cardiologist with 20 years of experience..."
}

When created, the backend embeds the text with whatever embedder this workspace resolves to (see below) and stores the vector in context_items.embedding. If the workspace has no embedder, or the one it has does not answer, the item is created without an embedding — it still works as a system prompt, it just will not be found by semantic search.

GET /api/context-items?offset=0&limit=50

Returns items owned by the current user plus shared items (where owner = 'all').

GET /api/context-items/search?q=heart+disease&limit=10

This endpoint:

  1. Embeds the query text with the workspace’s system embedder
  2. Performs a cosine similarity search using pgvector
  3. Returns results ranked by similarity score
SELECT *, 1 - (embedding <=> $1::vector) AS similarity
FROM context_items
WHERE (owner = 'all' OR owner = $2) AND embedding IS NOT NULL
ORDER BY embedding <=> $1::vector
LIMIT $3

The embedding column is an unsized vector, so switching to a model with a different number of dimensions needs no migration. It does need a re-vectorise, though — see the caution below.

Not Ollama’s, unless you chose Ollama’s. This page used to say embeddings came from a nomic-embed-text you had to pull yourself, which was true when the only embedder was a local Ollama and stopped being true when embedding moved to provider:model against any configured provider.

One place answers the question — EmbedderService — and it answers it twice, because a workspace has two embedder settings that are resolved separately:

SettingWhat it embeds
The system embedderContext items, prompt search, the docs index
The Vault embedderThe research corpus (see the Vault)

Each is stored as provider:model — novita:baai/bge-m3, ollama:nomic-embed-text, and so on — so the provider is part of the choice, not an assumption.

A separate table query_items stores pre-populated query suggestions with embeddings. The suggestions endpoint finds semantically similar queries:

GET /api/suggestions?q=explain+transformers&limit=5

Response:

[
{ "id": "...", "query": "How do transformer models work?", "similarity": 0.92 },
{ "id": "...", "query": "Explain attention mechanisms", "similarity": 0.87 }
]

Context items have an owner field:

  • 'all' — Shared with all users (read-only for non-owners)
  • User UUID string — Private to that user

RLS policies enforce this at the database level:

  • Anyone can SELECT items where owner = 'all' or owner matches their user ID
  • Users can only INSERT, UPDATE, and DELETE items they own

The ContextPicker.svelte component provides a search interface for context items when creating a new chat. It calls the search endpoint as the user types and displays results ranked by similarity. Selecting an item populates the context_text field in the Add Chat dialog.

The context_items table uses an IVFFlat index for fast approximate nearest neighbor search:

CREATE INDEX idx_context_items_embedding ON public.context_items
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);