mastra-ai/mastra · error · Error
The learner may record at most one skill per reflection.
Error message
The learner may record at most one skill per reflection.
What it means
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.
Source
Thrown at packages/memory/src/processors/observational-memory/subconscious/learn.ts:99
procedure: { type: 'string', minLength: 1 },
sourceRecordIds: { type: 'array', items: { type: 'string', minLength: 1 }, minItems: 2, uniqueItems: true },
},
required: ['name', 'procedure', 'sourceRecordIds'],
additionalProperties: false,
} satisfies JSONSchema7,
execute: async raw => {
const value = raw as { name: string; procedure: string; sourceRecordIds: string[] };
const sourceIds = [...new Set(value.sourceRecordIds)];
const pending = new Map(input.pendingRecords.map(record => [record.id, record]));
if (sourceIds.length < 2 || sourceIds.some(id => !pending.has(id))) {
throw new Error('Skill evidence requires at least two distinct records from the pending learner worklist.');
}
const normalizedName = value.name.trim();
if (
input.state.recordedName &&
input.state.recordedName.toLocaleLowerCase() !== normalizedName.toLocaleLowerCase()
) {
throw new Error('The learner may record at most one skill per reflection.');
}
input.state.recordedName = normalizedName;
const nodeScope = expandKnowledgeScope(input.scope, input.defaultScope);
let node = await input.store.resolveNode({ name: normalizedName, scope: input.scope });
if (node && node.kind !== 'skill') throw new Error(`Knowledge node is not a skill: ${normalizedName}`);
node ??= await input.store.createNode({ name: normalizedName, kind: 'skill', scope: nodeScope });
const evidence = [];
for (const sourceId of sourceIds) {
const id = evidenceRecordId(sourceId, normalizedName);
const existing = await input.store.getKnowledge({ id });
if (existing) {
evidence.push(existing);
continue;
}
const source = pending.get(sourceId)!;
try {
evidence.push(
await input.store.appendKnowledge({View on GitHub (pinned to 75dd419e61)
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
Example fix
// before
await tool.execute({ name: 'skill-a', procedure, sourceRecordIds });
await tool.execute({ name: 'skill-b', procedure2, sourceRecordIds2 }); // throws
// after
await tool.execute({ name: 'skill-a', procedure, sourceRecordIds });
// handle skill-b in the next reflection cycle Defensive patterns
Strategy: try-catch
Validate before calling
if (state.recordedName && state.recordedName.toLowerCase() !== name.trim().toLowerCase()) {
throw new Error('A skill was already recorded this reflection; defer to next cycle');
} Try / catch
try {
await tool.execute({ name, procedure, sourceRecordIds });
} catch (e) {
if (e instanceof Error && e.message.includes('at most one skill per reflection')) {
// skip; schedule this skill for the next reflection
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: LLM deciding to capture two procedures in one reflection; retry logic that rephrases the skill name; concurrent tool invocations sharing the same state object.
Related errors
- Skill evidence requires at least two distinct records from t
- Telegram ${method} failed: ${detail}
- MastraClient.deleteThread() requires exactly one of agentId
- No response body
- request failed with status ${response.status}: ${responseTex
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/99e5eb682fd7ac3e.
Report an issue: GitHub.