{"record":{"id":"99e5eb682fd7ac3e","repo":"mastra-ai/mastra","slug":"the-learner-may-record-at-most-one-skill-per-refle","errorCode":null,"errorMessage":"The learner may record at most one skill per reflection.","messagePattern":"The learner may record at most one skill per reflection\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/memory/src/processors/observational-memory/subconscious/learn.ts","lineNumber":99,"sourceCode":"        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) {\n          evidence.push(existing);\n          continue;\n        }\n        const source = pending.get(sourceId)!;\n        try {\n          evidence.push(\n            await input.store.appendKnowledge({","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/memory/src/processors/observational-memory/subconscious/learn.ts#L81-L117","documentation":"The learner records at most one skill per reflection. Once a skill name is recorded in the reflection state, any subsequent record-skill call with a different (case-insensitive) name throws this error. This constrains skill extraction to one procedure per reflection cycle, keeping the learned-skill log auditable and avoiding runaway skill creation.","triggerScenarios":"Calling the record-skill tool twice within the same reflection with different skill names; the reflection loop retrying with a renamed skill after the first call already succeeded.","commonSituations":"LLM deciding to capture two procedures in one reflection; retry logic that rephrases the skill name; concurrent tool invocations sharing the same state object.","solutions":["Record only one skill per reflection and defer additional skills to the next reflection cycle","Reuse the same skill name if updating/extending the skill already recorded this reflection","Check input.state.recordedName before calling and skip or no-op if a skill was already recorded"],"exampleFix":"// before\nawait tool.execute({ name: 'skill-a', procedure, sourceRecordIds });\nawait tool.execute({ name: 'skill-b', procedure2, sourceRecordIds2 }); // throws\n// after\nawait tool.execute({ name: 'skill-a', procedure, sourceRecordIds });\n// handle skill-b in the next reflection cycle","handlingStrategy":"try-catch","validationCode":"if (state.recordedName && state.recordedName.toLowerCase() !== name.trim().toLowerCase()) {\n  throw new Error('A skill was already recorded this reflection; defer to next cycle');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await tool.execute({ name, procedure, sourceRecordIds });\n} catch (e) {\n  if (e instanceof Error && e.message.includes('at most one skill per reflection')) {\n    // skip; schedule this skill for the next reflection\n  } else throw e;\n}","preventionTips":["Track one skill per reflection in your orchestration loop","Reuse the recorded skill name for updates within the same reflection","Normalize skill name casing before comparing across calls"],"tags":["memory","skill-learning","rate-limit"],"backgroundTag":"one-skill-per-reflection-limit","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}