headroomlabs-ai/headroom · error · ValueError

Memory {memory.id} has no embedding

Error message

Memory {memory.id} has no embedding

What it means

Raised by HNSWVectorIndex.add_memory (or add) when the Memory object passed in has memory.embedding set to None. The HNSW index only stores pre-computed vectors, so it refuses to index a memory that lacks one. Embedding must be produced by an external Embedder before indexing.

Source

Thrown at headroom/memory/adapters/hnsw.py:334

    def size(self) -> int:
        """Return the number of vectors currently indexed."""
        with self._lock:
            return len(self._memory_to_hnsw)

    async def index(self, memory: Memory) -> None:
        """Index a memory's embedding for similarity search.

        The memory must have an embedding set. If max_entries is set and
        the limit is reached, low-importance entries are evicted.

        Args:
            memory: The memory to index.

        Raises:
            ValueError: If the memory has no embedding or wrong dimension.
        """
        if memory.embedding is None:
            raise ValueError(f"Memory {memory.id} has no embedding")

        embedding = np.asarray(memory.embedding, dtype=np.float32)
        if embedding.shape[0] != self._dimension:
            raise ValueError(
                f"Embedding dimension {embedding.shape[0]} does not match "
                f"index dimension {self._dimension}"
            )

        with self._lock:
            # Check if already indexed - update if so
            if memory.id in self._memory_to_hnsw:
                await self._update_embedding_internal(memory.id, embedding)
                # Update metadata
                self._metadata[memory.id] = IndexedMemoryMetadata.from_memory(memory)
            else:
                # Evict if at capacity (before adding new entry)
                if self._max_entries is not None:
                    current_size = len(self._memory_to_hnsw)

View on GitHub (pinned to 322425c43b)

Solutions

  1. Run the memory through your Embedder (e.g. memory.embedding = await embedder.embed(memory.content)) before calling add_memory.
  2. Check memory.embedding is not None before indexing and route un-embedded memories to an embedding step.
  3. If embeddings are generated in a background task, await its completion before the index call.

Example fix

// before
await index.add_memory(memory)  # memory.embedding is None

// after
if memory.embedding is None:
    memory.embedding = await embedder.embed(memory.content)
await index.add_memory(memory)
Defensive patterns

Strategy: validation

Validate before calling

if memory.embedding is None:
    memory.embedding = await embedder.embed(memory.content)
await index.add_memory(memory)

Type guard

def has_embedding(m: Memory) -> bool:
    return m.embedding is not None

Try / catch

try:
    await index.add_memory(memory)
except ValueError as e:
    if "no embedding" in str(e):
        memory.embedding = await embedder.embed(memory.content)
        await index.add_memory(memory)
    else:
        raise

Prevention

When it happens

Trigger: Calling index.add_memory(memory) on a Memory that was never run through an Embedder; embedding a memory asynchronously and indexing before the embedding task finishes; loading memories from a store that did not persist embeddings.

Common situations: Pipeline ordering bugs where embed() and index() run in the wrong order; memories created from plain text and passed directly to the vector index; partial deserialization where the embedding column was null.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/85405548f60963ba. Report an issue: GitHub.