{"record":{"id":"0b3b325f0fd17019","repo":"mastra-ai/mastra","slug":"skill-evidence-requires-at-least-two-distinct-reco","errorCode":null,"errorMessage":"Skill evidence requires at least two distinct records from the pending learner worklist.","messagePattern":"Skill evidence requires at least two distinct records from the pending learner worklist\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/memory/src/processors/observational-memory/subconscious/learn.ts","lineNumber":92,"sourceCode":"    id: 'knowledge_record_skill',\n    description:\n      'Create or update one reusable skill using at least two distinct pending knowledge records. Evidence writes are idempotent across retries.',\n    inputSchema: {\n      type: 'object',\n      properties: {\n        name: { type: 'string', minLength: 1 },\n        procedure: { type: 'string', minLength: 1 },\n        sourceRecordIds: { type: 'array', items: { type: 'string', minLength: 1 }, minItems: 2, uniqueItems: true },\n      },\n      required: ['name', 'procedure', 'sourceRecordIds'],\n      additionalProperties: false,\n    } satisfies JSONSchema7,\n    execute: async raw => {\n      const value = raw as { name: string; procedure: string; sourceRecordIds: string[] };\n      const sourceIds = [...new Set(value.sourceRecordIds)];\n      const pending = new Map(input.pendingRecords.map(record => [record.id, record]));\n      if (sourceIds.length < 2 || sourceIds.some(id => !pending.has(id))) {\n        throw new Error('Skill evidence requires at least two distinct records from the pending learner worklist.');\n      }\n      const normalizedName = value.name.trim();\n      if (\n        input.state.recordedName &&\n        input.state.recordedName.toLocaleLowerCase() !== normalizedName.toLocaleLowerCase()\n      ) {\n        throw new Error('The learner may record at most one skill per reflection.');\n      }\n      input.state.recordedName = normalizedName;\n      const nodeScope = expandKnowledgeScope(input.scope, input.defaultScope);\n      let node = await input.store.resolveNode({ name: normalizedName, scope: input.scope });\n      if (node && node.kind !== 'skill') throw new Error(`Knowledge node is not a skill: ${normalizedName}`);\n      node ??= await input.store.createNode({ name: normalizedName, kind: 'skill', scope: nodeScope });\n      const evidence = [];\n      for (const sourceId of sourceIds) {\n        const id = evidenceRecordId(sourceId, normalizedName);\n        const existing = await input.store.getKnowledge({ id });\n        if (existing) {","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/memory/src/processors/observational-memory/subconscious/learn.ts#L74-L110","documentation":"The learner's record-skill tool demands evidence: the cited sourceRecordIds must contain at least two distinct IDs, and every ID must belong to the pending learner worklist for the current reflection. This prevents the learner from minting skills (procedural knowledge) from a single observation or from records it was never shown.","triggerScenarios":"Calling the record-skill tool with fewer than two unique record IDs, duplicate IDs that dedupe to one, or IDs not present in the pendingRecords worklist passed to createLearnerRecordSkillTool (e.g. records from another thread or already-consumed records).","commonSituations":"LLM citing record IDs it hallucinated or saw in earlier reflections; duplicates in the ID array collapsing below two after dedupe; worklist rotated between planning and tool execution.","solutions":["Pick at least two distinct record IDs from the pending worklist handed to the learner in the current reflection","Dedupe and verify each ID against the pending list before invoking the tool","Re-run the reflection if the worklist has changed so the learner gets fresh record IDs"],"exampleFix":"// before\nawait tool.execute({ name, procedure, sourceRecordIds: [id] });\n// after\nconst ids = [...new Set(pending.map(r => r.id))].slice(0, 2);\nif (ids.length < 2) throw new Error('Need >=2 distinct pending records');\nawait tool.execute({ name, procedure, sourceRecordIds: ids });","handlingStrategy":"validation","validationCode":"const ids = [...new Set(sourceRecordIds)];\nconst pendingIds = new Set(pendingRecords.map(r => r.id));\nif (ids.length < 2 || !ids.every(id => pendingIds.has(id))) {\n  throw new Error('Need >=2 distinct records from the pending worklist');\n}","typeGuard":"function hasEnoughEvidence(ids: string[], pending: { id: string }[]): ids is [string, string, ...string[]] {\n  const set = new Set(pending.map(p => p.id));\n  const distinct = ids.filter(id => set.has(id));\n  return distinct.length >= 2;\n}","tryCatchPattern":"try {\n  await tool.execute({ name, procedure, sourceRecordIds });\n} catch (e) {\n  if (e instanceof Error && e.message.includes('at least two distinct records')) {\n    // re-pick evidence from the current pending worklist\n  } else throw e;\n}","preventionTips":["Dedupe sourceRecordIds before calling","Only cite records from the current reflection's pending worklist","Instruct the learner model to gather >=2 observations before recording a skill"],"tags":["memory","validation","skill-learning"],"backgroundTag":"insufficient-evidence-for-skill","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}