NuggetMD: Format Specification v0.3
Part of the LearnSpec suite. Draft.
Core Principle
NuggetMD is the micro-learning format of the LearnSpec suite. A .nugget.md file hosts a collection of nuggets, short, self-contained concepts designed to be read in under 3 minutes each and reviewed over time via spaced repetition.
The author decides how many nuggets to put in a file and when to split into multiple files. A file may contain a single nugget or dozens. Structure follows the topic, not an arbitrary file-per-concept rule.
NuggetMD occupies a distinct position between FlashMD and LearnMD:
| FlashMD | NuggetMD | LearnMD | |
|---|---|---|---|
| Unit | Atomic fact | Applicable concept | Full lesson |
| Review time | 5–30 sec | 1–3 min | 10–60 min |
| Memory type | Declarative, knowing that | Procedural, knowing how / when | Structured instruction |
| File structure | Many cards per file | Many nuggets per file | One document per file |
NuggetMD inherits its frontmatter and validation rules from the shared LearnSpec Architecture Charter.
| Principle | Description |
|---|---|
| Markdown-first | A .nugget.md file is valid Markdown readable in any editor |
| File-native | All content lives in files, no database required |
| Graceful degradation | Renders as a readable multi-section article in any standard reader |
| Author-controlled granularity | The author decides how many nuggets per file and when to split |
| FSRS-ready | Each nugget can independently enter a spaced repetition queue |
| AI-native | Generatable and consumable by an LLM without specific tooling |
Format Levels
| Level | Mechanism | Purpose |
|---|---|---|
| 0 | ## headings + ### sub-sections | Nugget collection, readable everywhere |
| 1 | YAML frontmatter + per-nugget nugget block | Metadata, FSRS, per-nugget attributes |
| 2 | Recall question in the third ### section (QuizMD Level 0 syntax) | FSRS review mechanism |
Level 0: Heading-Based Structure
Each nugget is a ## heading. Its sub-sections are ### headings, and their order carries their role: the labels themselves are free text, in any language. The file reads as a natural Markdown document, no tooling required.
# Python Best Practices
## Prefer enumerate() over range(len())
### Concept
When iterating over a list and needing both the index and the value,
`enumerate()` is the idiomatic Python choice. It returns an iterator of
`(index, value)` pairs, making your code more readable and slightly faster.
### Why it matters
Next time you write `for i in range(len(...))`, stop and ask: do I need
both the index and the value? If yes, switch to `enumerate()`.
---
## Use zip() to iterate over multiple lists simultaneously
### Concept
`zip()` takes two or more iterables and returns an iterator of tuples,
pairing elements at the same index across all iterables.
### Why it matters
Instead of managing indices manually to combine two lists, `zip()` makes
the intent immediately clear and eliminates off-by-one errors.Structure rules
- The
# H1is the file title, optional, inferred from frontmattertitleif absent. - Each
## H2heading opens one nugget. The heading text is the nugget title. - Inside each nugget, up to three
### H3sub-sections. Position determines the role; the heading text is free:
| Position | Role | Required | Purpose |
|---|---|---|---|
1st ### | Concept | Yes | The concept, explained in 2–5 sentences |
2nd ### | Why it matters | Yes | One concrete example or actionable takeaway |
3rd ### | Check | No, strongly recommended when FSRS is active | One recall question |
- Labels are not parsed.
### Concept,### Le conceptand### Was ist das?are all the first section. A parser takes the first three###headings of a nugget in document order and assigns the roles above; it never matches on the heading text.Concept/Why it matters/Checkremain the recommended English labels for interchange, and files written against v0.2 parse identically under this rule. - A fourth or further
###inside a nugget carries no role: it is a warning, and players render its content as part of the nugget after the third section.
The same nugget, authored in French, no marker changes, and a parser needs no knowledge of French:
## Préférer enumerate() à range(len())
### Le concept
Quand on parcourt une liste en ayant besoin de l'indice *et* de la valeur,
`enumerate()` est la façon idiomatique de le faire en Python.
### Pourquoi c'est important
La prochaine fois que vous écrivez `for i in range(len(...))`, demandez-vous
si vous avez besoin des deux. Si oui, passez à `enumerate()`.
### Question de rappel
? Quelle fonction native donne à la fois l'indice et la valeur ?
- [x] enumerate()
- [ ] range(len())- A
---horizontal rule between nuggets is optional but strongly recommended for visual clarity. - No
####or deeper headings inside a nugget, if more structure is needed, the content belongs in LearnMD.
Level 1: Frontmatter and Per-Nugget Metadata
File-level frontmatter
---
title: "Python Best Practices" # optional: inferred from # H1
lang: en # REQUIRED: BCP-47 code
tags: [python, best-practices] # optional: file-level tags
spaced_repetition: fsrs # optional: fsrs | sm2 | false (default: false)
lesson: ./03-iteration.learn.md # optional: default source lesson (v0.2)
author: Jane Smith # optional
created: 2026-05-10 # optional: ISO 8601
updated: 2026-05-10 # optional: ISO 8601
license: CC-BY-4.0 # optional: SPDX identifier or custom
spec_version: "0.1" # optional
---spaced_repetition at file level sets the default for all nuggets in the file. It can be overridden per nugget.
Per-nugget metadata block
An optional empty fenced nugget block placed immediately after the ## heading carries per-nugget attributes. It contains no body, attributes only.
## Prefer enumerate() over range(len())
```nugget id:enumerate tags:[iteration] level:beginner related:[zip-function,unpacking]
```
### Concept
...In a standard Markdown reader, the empty fenced block renders as a small empty code block, readable and unobtrusive. A LearnSpec parser extracts the attributes; the player may render them as a discrete metadata badge.
Per-nugget attribute reference
| Attribute | Status | Type | Description |
|---|---|---|---|
id | Recommended | string | Stable unique slug within the file. Auto-generated from the heading slug if absent. Enables precise FSRS tracking. |
tags | Optional | string[] | Per-nugget tags. Added to file-level tags. |
level | Optional | enum | beginner, intermediate, advanced. Overrides file-level default. |
spaced_repetition | Optional | enum | Per-nugget override: fsrs, sm2, or false. Overrides file-level setting. |
related | Optional | string[] | Slugs of related nuggets within the same file or other .nugget.md files. Informational only, no import dependency. The player may surface these as "next nugget" suggestions. |
lesson | Optional | string | Reference to the lesson that explains this nugget (v0.2). See Lesson References. |
Lesson References (Level 1)
A nugget may point back to the lesson that develops it, so a learner can jump from a micro-concept to the full course. Unlike related (nugget→nugget, informational), lesson is a navigation reference to a .learn.md section. It uses hyperlink-style resolution: the same mental model as an HTML/Markdown link, combining a file-level default with per-nugget overrides:
lesson value | Resolves to | Like an HTML link… |
|---|---|---|
| (absent) | The file-level default lesson, at its top | implicit link |
#section-anchor | The file default lesson, at that section | <a href="#frag"> |
./other.learn.md | Another lesson, at its top | <a href="page.html"> |
./other.learn.md#anchor | Another lesson, at that section | <a href="page.html#frag"> |
The value is a relative path to a .learn.md file, optionally suffixed with #<anchor>. The anchor resolves against a LearnMD heading slug or an explicit !checkpoint id. The value carries its own path, so no !ref declaration is required.
---
title: "Iteration in Python"
lang: en
lesson: ./03-iteration.learn.md # default for every nugget in the file
---
# Iteration in Python
## Prefer enumerate() over range(len())
```nugget id:enumerate tags:[iteration] lesson:#the-enumerate-builtin
```
### Concept
...The nugget above links to the the-enumerate-builtin section of the default lesson; a nugget with no lesson links to the lesson's top, and lesson:./other.learn.md#x overrides the target entirely.
Player behaviour. The player SHOULD surface the reference as a "back to the lesson" link (typically after the third section), open the target within its track context when one exists (falling back to the standalone lesson view), and, if it navigates in place, make returning to the review session obvious. The #anchor SHOULD resolve to a stable identifier (!checkpoint id or explicit heading anchor) before falling back to the heading slug.
Level 2: Recall Question
The third ### section, conventionally labelled ### Check, contains a single recall question in QuizMD Level 0 syntax: no fenced block wrapper, no scoring configuration.
### Check
? Which built-in function gives you both the index and the value
when iterating over a list?
- [x] enumerate()
- [ ] range(len())
- [ ] zip()
- [ ] iter()This is the FSRS recall mechanism. After reading the nugget, the learner answers, then rates their confidence. No scoring, no feedback_mode, recall rating only.
Complete Example
---
title: "Python Best Practices"
lang: en
tags: [python, best-practices]
spaced_repetition: fsrs
author: Jane Smith
created: 2026-05-10
license: CC-BY-4.0
spec_version: "0.1"
---
# Python Best Practices
## Prefer enumerate() over range(len())
```nugget id:enumerate tags:[iteration] level:beginner related:[zip-function]
```
### Concept
When iterating over a list and needing both the index and the value,
`enumerate()` is the idiomatic Python choice. It returns an iterator of
`(index, value)` pairs, making your code shorter and more readable.
```python
# ❌ Avoid
for i in range(len(fruits)):
print(i, fruits[i])
# ✅ Prefer
for i, fruit in enumerate(fruits):
print(i, fruit)
```
You can also set a custom start index: `enumerate(fruits, start=1)`.
### Why it matters
Next time you write `for i in range(len(...))`, stop and ask: do I need
both the index and the value? If yes, switch to `enumerate()`. Your code
reviewer will thank you.
### Check
? Which function gives you both the index and the value when iterating?
- [x] enumerate()
- [ ] range(len())
- [ ] zip()
- [ ] index()
---
## Use zip() to iterate over multiple lists simultaneously
```nugget id:zip-function tags:[iteration] level:beginner related:[enumerate,unpacking]
```
### Concept
`zip()` takes two or more iterables and returns an iterator of tuples,
pairing elements at the same position. It stops at the shortest iterable.
```python
names = ["Alice", "Bob", "Carol"]
scores = [88, 92, 79]
for name, score in zip(names, scores):
print(f"{name}: {score}")
```
### Why it matters
Instead of managing a shared index across two lists, `zip()` makes the
pairing explicit and eliminates off-by-one errors entirely.
### Check
? What does zip() do when the two iterables have different lengths?
- [x] It stops at the shortest one
- [ ] It raises a ValueError
- [ ] It pads the shorter one with None
- [ ] It stops at the longest one
---
## Unpack tuples directly in loops
```nugget id:unpacking tags:[iteration,tuples] level:intermediate
```
### Concept
When iterating over a list of tuples, Python lets you unpack each tuple
directly in the `for` statement, eliminating indexed access and making
variable names self-documenting.
```python
# ❌ Less clear
for item in coordinates:
print(item[0], item[1])
# ✅ Clearer
for x, y in coordinates:
print(x, y)
```
### Why it matters
Tuple unpacking pairs naturally with `enumerate()` and `zip()`, both
produce tuples. Combining them gives you expressive, readable iteration
with zero boilerplate.
### Check
? Which syntax correctly unpacks a tuple in a for loop?
- [x] for x, y in pairs:
- [ ] for (x, y) = pairs:
- [ ] for pair.unpack() in pairs:
- [ ] for x and y in pairs:Reading-Time Constraint
The 3-minute limit applies per nugget, not per file. A file with 20 nuggets is valid as long as each individual nugget reads in under 3 minutes.
| Condition | Level |
|---|---|
| Estimated reading time of a single nugget > 3 min | Error |
| Estimated reading time of a single nugget > 2.5 min | Warning |
Estimation: word_count / 200 wpm, rounded up to the nearest 30 seconds. If a nugget exceeds the limit, the content belongs in LearnMD.
FSRS Integration
When spaced_repetition: fsrs is active (file-level or per-nugget), each nugget enters its own independent FSRS slot in the player's review queue. Nuggets within the same file are tracked individually.
1. Learner reads the nugget (Concept + Why it matters)
2. Learner answers the ### Check question (if present)
3. Learner rates recall confidence:
Again (1), didn't remember
Hard (2), remembered with effort
Good (3), remembered correctly
Easy (4), instantly recalled
4. FSRS calculates the next review interval for this specific nugget
5. Nugget is scheduled, other nuggets in the same file are unaffectedFlashMD and NuggetMD feed separate FSRS queues: never mixed:
| FlashMD | NuggetMD | |
|---|---|---|
| Review duration | ~10 sec | ~2 min |
| Typical "Easy" interval | 4–7 days | 14–30 days |
| Player queue label | Cards | Concepts |
Interoperability
| Mechanism | Support |
|---|---|
Imported by TrackMD via !import | ✅ |
Imported by LearnMD via !import | ✅ |
!ref ./curriculum.curriculum.md | ✅, declares curriculum alignment |
!ref ./glossary.glossary.md | ✅, enables term highlighting |
!ref ./media.media.md | ✅, enables media:slug resolution |
lesson: reference to a .learn.md section | ✅, navigation only; resolves a path, declares no dependency (v0.2) |
!import of other formats | ❌, leaf format |
| FSRS (player-managed, per nugget) | ✅ when spaced_repetition is set |
In a TrackMD
## Section 3: Iteration in Python
!import ./03-iteration.learn.md
!import ./quiz-iteration.quiz.md
!import ./flashcards-iteration.flash.md
!import ./nuggets-iteration.nugget.md optional:trueValidation
Lenient Mode (default)
| Condition | Level |
|---|---|
lang absent from frontmatter | Warning |
Fewer than two ### sections in a nugget | Error |
More than one recall question in the third ### section | Error |
Duplicate id within the file | Error |
No third ### section when spaced_repetition is active on that nugget | Warning |
| Estimated reading time of a nugget > 2.5 min | Warning |
| Estimated reading time of a nugget > 3 min | Error |
#### or deeper heading inside a nugget | Warning |
More than three ### sections in a nugget | Warning |
lesson value whose path does not end in .learn.md | Error |
Nugget lesson:#anchor (anchor-only) with no file-level lesson default to resolve it | Error |
lesson anchor not found in the target lesson | Warning, checked by the collection-level validator (cross-file) |
Strict Mode (--strict)
All warnings are promoted to errors.
Deferred Decisions
| Feature | Reason for deferral |
|---|---|
Contextual triggers (event: before_meeting) | Player implementation complexity |
| Audio / video nuggets | Binary content outside Markdown scope |
Cross-file related resolution | Requires corpus-level indexing, player concern |
v0.3 made the
###sub-sections positional: their role comes from their order, not from fixed English labels, so a nugget can be authored in any language. v0.2 added thelessonreference (links a nugget back to its source lesson section).
Released under the MIT License. Copyright © 2024-present LearnSpec Contributors