mastra-ai/mastra · error
Multi-thread observer produced degenerate output after retry
Error message
Multi-thread observer produced degenerate output after retry. ${describeDegenerateOutput(result.text)} What it means
`ObserverRunner.callMultiThread` runs the multi-thread Observer variant that summarizes several threads at once. Its output is parsed by `parseMultiThreadObserverOutput`, which detects degenerate repetition; if the automatic single retry also yields degenerate output, this error is thrown (observer-runner.ts:543). It protects cross-thread memory from being overwritten by a model that fell into a repetition loop.
Source
Thrown at packages/memory/src/processors/observational-memory/observer-runner.ts:543
);
};
let result = await doGenerate();
let parsed = parseMultiThreadObserverOutput(result.text, activeExtractors);
let retriedDueToDegenerate = false;
if (parsed.degenerate) {
omDebug(
`[OM:callMultiThreadObserver] degenerate repetition detected, retrying once. ${describeDegenerateOutput(result.text, 2000)}`,
);
result = await doGenerate();
parsed = parseMultiThreadObserverOutput(result.text, activeExtractors);
retriedDueToDegenerate = true;
if (parsed.degenerate) {
omDebug(
`[OM:callMultiThreadObserver] degenerate repetition on retry, failing. ${describeDegenerateOutput(result.text, 2000)}`,
);
throw new Error(
`Multi-thread observer produced degenerate output after retry. ${describeDegenerateOutput(result.text)}`,
);
}
}
const structuredExtractionByThread = new Map<string, Awaited<ReturnType<typeof extractStructuredValues>>>();
const aggregatedExtractedValues = mergeExtractedValues(
...Array.from(parsed.threads, ([threadId, threadResult]) =>
mergeExtractedValues(threadResult.extractedValues, structuredExtractionByThread.get(threadId)?.values),
),
);
const aggregatedExtractionFailures = mergeExtractionFailures(
...Array.from(parsed.threads, ([threadId, threadResult]) =>
mergeExtractionFailures(threadResult.extractionFailures, structuredExtractionByThread.get(threadId)?.failures),
),
);
const aggregatedBuiltIns = getBuiltInExtractedValues(aggregatedExtractedValues);View on GitHub (pinned to 75dd419e61)
Solutions
- Switch to a stronger observation model via `observationModel`, or adjust `routingThresholds`/`routingStrategy` so large multi-thread batches route to a capable model.
- Reduce batch size: observe threads more frequently or lower per-cycle thread/token limits so the multi-thread prompt fits comfortably in context.
- Add repetition-curbing `modelSettings` (frequencyPenalty/repetitionPenalty, moderate temperature) to the observation config.
- Inspect the `describeDegenerateOutput` snippet in the message to identify the looping content, and check whether a specific thread's content triggers it (move that thread to single-thread observation or exclude it).
Example fix
// before
new ObservationalMemory({
observationModel: 'ollama/qwen2.5:0.5b',
perResource: { enabled: true }, // multi-thread batches overflow a small model
});
// after
new ObservationalMemory({
observationModel: 'openai/gpt-4o-mini',
perResource: { enabled: true },
modelSettings: { frequencyPenalty: 0.5 },
}); Defensive patterns
Strategy: fallback
Validate before calling
// before enabling multi-thread observation, ensure the model can handle the combined prompt
const totalTokens = threads.reduce((n, t) => n + countTokens(t.messages), 0);
if (totalTokens > modelMaxInputTokens * 0.6) {
throw new Error('multi-thread batch too large for observation model; reduce batch or use stronger model');
} Type guard
function looksDegenerate(text: string): boolean {
const chunks = text.split(/\n{2,}/).map(c => c.trim()).filter(Boolean);
if (chunks.length < 3) return false;
return new Set(chunks).size / chunks.length < 0.5;
} Try / catch
try {
await memory.process(messages, { abortSignal: signal });
} catch (err) {
if (err instanceof Error && err.message.startsWith('Multi-thread observer produced degenerate output')) {
logger.warn('multi-thread observer looping; falling back to per-thread observation');
await processPerThread(messages, { abortSignal: signal });
return;
}
throw err;
} Prevention
- Route large multi-thread batches to a strong model via routingStrategy/routingThresholds.
- Limit threads-per-batch and total input tokens so the prompt fits with headroom.
- Add repetition penalties to observer modelSettings.
- Pin down which thread triggers looping by logging batch contents when this error occurs.
When it happens
Trigger: Multi-thread observation is enabled (per-resource observation across multiple threads) and the observation model returns repetition-looped text for the combined multi-thread prompt; the built-in one-shot retry in `callMultiThread` also parses as degenerate, so the batch fails. More likely with small/quantized models given the larger, more complex multi-thread prompt.
Common situations: Running observation across many long threads with a local 1-3B model that loops on the large prompt; context-window overflow when batching multiple threads (truncated output triggers repetition); misconfigured routing thresholds selecting a weak model for large inputs; provider degradation returning repetitive filler.
Related errors
- Observer produced degenerate output after retry. ${describeD
- Extractor "${extractor.slug}" output did not match its schem
- ${EXTRACTED_VALUES_TAG} must contain a JSON object.
- Curator did not acknowledge a valid processed KnowledgeRecor
- Learner did not acknowledge a valid reviewed record cursor.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/51ff22951313a96c.
Report an issue: GitHub.