mastra-ai/mastra · warning · StaleKnowledgeSemanticIndexError

Knowledge semantic index remained stale after ${MAX_DRAIN_BA

Error message

Knowledge semantic index remained stale after ${MAX_DRAIN_BATCHES} processing batches.

What it means

`#drain` (semantic-index.ts:151) caps how many outbox batches it processes per search (`MAX_DRAIN_BATCHES`). If the outbox still has visible work after that many batches, it throws `StaleKnowledgeSemanticIndexError` rather than blocking the request indefinitely — the backlog is too large to drain within one search call.

Source

Thrown at packages/memory/src/processors/observational-memory/subconscious/semantic-index.ts:151

      for (let index = 0; index < entries.length; index++) {
        const entry = entries[index]!;
        try {
          await this.#apply(entry);
          await this.#knowledge.completeSemanticOutbox({ ids: [entry.id], workerId: this.#workerId });
          processed++;
        } catch (error) {
          await this.#knowledge.releaseSemanticOutbox({
            ids: entries.slice(index).map(pendingEntry => pendingEntry.id),
            workerId: this.#workerId,
          });
          throw new StaleKnowledgeSemanticIndexError(
            `Knowledge semantic index is stale because operation ${entry.id} could not be applied.`,
            { cause: error },
          );
        }
      }
    }
    throw new StaleKnowledgeSemanticIndexError(
      `Knowledge semantic index remained stale after ${MAX_DRAIN_BATCHES} processing batches.`,
    );
  }

  async #apply(entry: KnowledgeSemanticOutboxEntry): Promise<void> {
    if (entry.operation === 'delete') {
      await this.#deleteDocument(entry.documentId);
      return;
    }

    const document = await this.#loadDocument(entry);
    if (!document) {
      await this.#deleteDocument(entry.documentId);
      return;
    }
    const result = await this.#embedder.doEmbed({
      values: [document.text],
      ...(this.#embedderOptions ?? {}),

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Run the indexer/drain as a background job first, then search once the outbox is empty.
  2. Increase throughput (batch embedding, faster embedder/vector store) or tune MAX_DRAIN_BATCHES if appropriate.
  3. Check for stuck 'processing' entries from crashed workers and release them so drains make progress.
  4. Retry with backoff — each attempt drains more of the backlog.

Example fix

// before
await bulkImport(docs);
await remind(context); // StaleKnowledgeSemanticIndexError: backlog too big
// after
await bulkImport(docs);
await backgroundDrainWorker.runToCompletion(); // drain fully before searching
await remind(context);
Defensive patterns

Strategy: retry

Validate before calling

const backlog = await store.listSemanticOutbox({ status: 'pending', scope, limit: 1000 });
if (backlog.length > 100) console.warn(`semantic outbox backlog (${backlog.length}) may exceed drain budget`);

Try / catch

try {
  return await remind(context);
} catch (e) {
  if (e instanceof StaleKnowledgeSemanticIndexError && e.message.includes('remained stale after')) {
    await backgroundDrain(); // drain off the request path, then retry once
    return remind(context);
  }
  throw e;
}

Prevention

When it happens

Trigger: A large backlog of pending semantic outbox operations (bulk import, mass deletes, many threads' knowledge) exceeding MAX_DRAIN_BATCHES, or a slow embedder/vector store making each batch small so the queue never empties within the budget.

Common situations: Bulk-loading knowledge then immediately searching; high-latency embedding providers; many concurrent writers generating outbox entries faster than one drain can process.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/4e95d749a63e24b3. Report an issue: GitHub.