mastra-ai/mastra · error · MastraError

OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED

OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED

Error message

OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED: ObservationalMemory (scope: 'thread') requires a threadId, but none was found in RequestContext or MessageList. Ensure the agent is configured with Memory and a valid threadId is provided.

What it means

In `scope: 'thread'` mode, ObservationalMemory keys all its state per thread. Without a threadId it would silently fall back to a resource-keyed record, which deadlocks when multiple threads share one resourceId. The processor therefore throws a MastraError (OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED, USER category) when it cannot find a threadId in either the RequestContext or the MessageList.

Source

Thrown at packages/memory/src/processors/observational-memory/observational-memory.ts:1820

        threadId: memoryContext.thread.id,
        resourceId: memoryContext.resourceId,
      };
    }

    // Fallback to MessageList's memoryInfo
    const serialized = messageList.serialize();
    if (serialized.memoryInfo?.threadId) {
      return {
        threadId: serialized.memoryInfo.threadId,
        resourceId: serialized.memoryInfo.resourceId,
      };
    }

    // In thread scope, threadId is required — without it OM would silently
    // fall back to a resource-keyed record which causes deadlocks when
    // multiple threads share the same resourceId.
    if (this.scope === 'thread') {
      throw new MastraError({
        id: 'OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED',
        domain: ErrorDomain.MASTRA_MEMORY,
        category: ErrorCategory.USER,
        details: { status: 400 },
        text:
          `ObservationalMemory (scope: 'thread') requires a threadId, but none was found in RequestContext or MessageList. ` +
          `Ensure the agent is configured with Memory and a valid threadId is provided.`,
      });
    }

    return null;
  }

  /**
   * Save messages to storage, skipping messages that were already persisted by
   * async buffering. Uses the message-level sealed flag (metadata.mastra.sealed)
   * to detect already-persisted messages, avoiding redundant DB operations.
   *

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Create/reuse a thread and pass its threadId (e.g. via RequestContext or by including thread-scoped messages in the MessageList).
  2. If per-thread state is not needed, switch ObservationalMemory to `scope: 'resource'`.
  3. Ensure the agent is configured with a working Memory instance so threadId resolution has a source.
  4. In API layers, require and forward the threadId on every request before invoking the agent.

Example fix

// before
await agent.generate('hello'); // no thread context
// after
await agent.generate('hello', {
  memory: { thread: threadId, resource: resourceId },
});
Defensive patterns

Strategy: try-catch

Validate before calling

const threadId = requestContext.get?.('threadId') ?? messages.find(m => m.threadId)?.threadId;
if (memory?.observational?.scope === 'thread' && !threadId) {
  throw new Error('scope: thread requires a threadId');
}

Type guard

function hasThreadId(ctx: { threadId?: string }): ctx is { threadId: string } {
  return typeof ctx.threadId === 'string' && ctx.threadId.length > 0;
}

Try / catch

try {
  await agent.generate(input, options);
} catch (e) {
  if (e instanceof MastraError && e.id === 'OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED') {
    // create a thread and retry with explicit memory context
    const thread = await memory.createThread({ resourceId });
    return agent.generate(input, { ...options, memory: { thread: thread.id, resource: resourceId } });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `agent.generate`/`agent.stream` (or memory retrieval paths) against an agent whose memory uses ObservationalMemory with `scope: 'thread'`, while passing no threadId in RequestContext and no thread-scoped message in the MessageList (observational-memory.ts:1820).

Common situations: Fire-and-forget scripts or cron jobs that call the agent without creating/passing a thread; API routes that drop the threadId header; tests that construct RequestContext without thread metadata; migrating from resource-scoped OM to thread-scoped without updating callers.

Understand the failure class

Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


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