{"record":{"id":"aff1f26678dce13f","repo":"mastra-ai/mastra","slug":"knowledge-curation-cursor-cannot-move-backwards","errorCode":null,"errorMessage":"Knowledge curation cursor cannot move backwards","messagePattern":"Knowledge curation cursor cannot move backwards","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/storage/domains/knowledge/inmemory.ts","lineNumber":512,"sourceCode":"      });\n    }\n    return results.slice(0, input.limit ?? 20);\n  }\n\n  async getCurationCursor(input: { sourceThreadId: string; agent: string }): Promise<KnowledgeCurationCursor | null> {\n    const cursor = this.#db.knowledgeCursors.get(`${input.sourceThreadId}\\u0000${input.agent}`);\n    return cursor ? { ...cursor, updatedAt: new Date(cursor.updatedAt) } : null;\n  }\n\n  async advanceCurationCursor(input: {\n    sourceThreadId: string;\n    agent: string;\n    lastKnowledgeId: string;\n  }): Promise<KnowledgeCurationCursor> {\n    const key = `${input.sourceThreadId}\\u0000${input.agent}`;\n    const existing = this.#db.knowledgeCursors.get(key);\n    if (existing && input.lastKnowledgeId < existing.lastKnowledgeId)\n      throw new Error('Knowledge curation cursor cannot move backwards');\n    const cursor = { ...input, updatedAt: new Date() };\n    this.#db.knowledgeCursors.set(key, cursor);\n    return { ...cursor };\n  }\n\n  async listActivity(input: {\n    scope: KnowledgeScope;\n    after?: string;\n    limit?: number;\n  }): Promise<KnowledgeActivityEvent[]> {\n    const queryScope = canonicalizeKnowledgeScope(input.scope);\n    return this.#db.knowledgeActivity\n      .filter(event => isKnowledgeScopeVisible(event.scope, queryScope))\n      .filter(event => !input.after || event.id < input.after)\n      .sort((a, b) => b.id.localeCompare(a.id))\n      .slice(0, input.limit ?? 100)\n      .map(event => ({ ...event, scope: [...event.scope], createdAt: new Date(event.createdAt) }));\n  }","sourceCodeStart":494,"sourceCodeEnd":530,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/storage/domains/knowledge/inmemory.ts#L494-L530","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait storage.advanceCurationCursor({ sourceThreadId, agent, lastKnowledgeId: batch.first.id });\n// after\nconst latest = batch.records.reduce((a, r) => (r.id > a ? r.id : a), batch.records[0]?.id);\nawait storage.advanceCurationCursor({ sourceThreadId, agent, lastKnowledgeId: latest });","handlingStrategy":"validation","validationCode":"const prev = cursors.get(`${sourceThreadId}\\u0000${agent}`);\nif (prev && nextLastKnowledgeId <= prev.lastKnowledgeId) return; // no-op, already advanced","typeGuard":null,"tryCatchPattern":"try {\n  await storage.advanceCurationCursor(input);\n} catch (e) {\n  if (e.message.includes('cannot move backwards')) return; // stale writer; ignore\n  throw e;\n}","preventionTips":["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."],"tags":["knowledge","cursor","curation","ordering"],"backgroundTag":"cursor-moved-backwards","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}