mastra-ai/mastra · error
Knowledge curation cursor cannot move backwards
Error message
Knowledge curation cursor cannot move backwards
What it means
advanceCurationCursor() persists a per (sourceThreadId, agent) cursor over knowledge record IDs. Because cursors track progress through an ordered ID stream (ULIDs sort lexicographically by time), moving the cursor backwards would re-process or rewind curation work, so the method throws if input.lastKnowledgeId is lexicographically less than the stored cursor's ID.
Source
Thrown at packages/core/src/storage/domains/knowledge/inmemory.ts:512
});
}
return results.slice(0, input.limit ?? 20);
}
async getCurationCursor(input: { sourceThreadId: string; agent: string }): Promise<KnowledgeCurationCursor | null> {
const cursor = this.#db.knowledgeCursors.get(`${input.sourceThreadId}\u0000${input.agent}`);
return cursor ? { ...cursor, updatedAt: new Date(cursor.updatedAt) } : null;
}
async advanceCurationCursor(input: {
sourceThreadId: string;
agent: string;
lastKnowledgeId: string;
}): Promise<KnowledgeCurationCursor> {
const key = `${input.sourceThreadId}\u0000${input.agent}`;
const existing = this.#db.knowledgeCursors.get(key);
if (existing && input.lastKnowledgeId < existing.lastKnowledgeId)
throw new Error('Knowledge curation cursor cannot move backwards');
const cursor = { ...input, updatedAt: new Date() };
this.#db.knowledgeCursors.set(key, cursor);
return { ...cursor };
}
async listActivity(input: {
scope: KnowledgeScope;
after?: string;
limit?: number;
}): Promise<KnowledgeActivityEvent[]> {
const queryScope = canonicalizeKnowledgeScope(input.scope);
return this.#db.knowledgeActivity
.filter(event => isKnowledgeScopeVisible(event.scope, queryScope))
.filter(event => !input.after || event.id < input.after)
.sort((a, b) => b.id.localeCompare(a.id))
.slice(0, input.limit ?? 100)
.map(event => ({ ...event, scope: [...event.scope], createdAt: new Date(event.createdAt) }));
}View on GitHub (pinned to 75dd419e61)
Solutions
- Only advance cursors from records listed after the current cursor position (use listKnowledge pagination).
- Load the existing cursor first and skip the update if input.lastKnowledgeId <= existing.lastKnowledgeId.
- Ensure a single writer per (sourceThreadId, agent) or serialize cursor updates.
- If a genuine rewind is needed, delete/rekey the cursor rather than advancing it backwards.
Example fix
// before
await storage.advanceCurationCursor({ sourceThreadId, agent, lastKnowledgeId: batch.first.id });
// after
const latest = batch.records.reduce((a, r) => (r.id > a ? r.id : a), batch.records[0]?.id);
await storage.advanceCurationCursor({ sourceThreadId, agent, lastKnowledgeId: latest }); Defensive patterns
Strategy: validation
Validate before calling
const prev = cursors.get(`${sourceThreadId}\u0000${agent}`);
if (prev && nextLastKnowledgeId <= prev.lastKnowledgeId) return; // no-op, already advanced Try / catch
try {
await storage.advanceCurationCursor(input);
} catch (e) {
if (e.message.includes('cannot move backwards')) return; // stale writer; ignore
throw e;
} Prevention
- Iterate knowledge IDs in ascending ULID order when advancing cursors.
- Persist the max ID of a batch, not the first.
- Assign one cursor writer per (sourceThreadId, agent).
- Reconcile checkpoints against the store after restores/replays.
When it happens
Trigger: Calling advanceCurationCursor() with lastKnowledgeId older than the currently stored cursor for the same sourceThreadId+agent pair — e.g., re-running an old batch, resuming from a stale checkpoint, or iterating records in non-ULID order.
Common situations: Two workers curating the same thread concurrently and the slower one writing an older cursor; replaying an event log from the beginning; restoring checkpoints from a backup while the store advanced.
Related errors
- Invalid knowledge node cursor.
- Knowledge node cursor does not match the active browse filte
- CursorSDKAgent resumeData must include a message.
- CursorSDKAgent resumeData.agentId must be a string when prov
- CursorSDKAgent does not support structuredOutput because the
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/aff1f26678dce13f.
Report an issue: GitHub.