# Overview Source: https://selflearningplatform.github.io/docs/overview SLP is a self-hosted study environment you fully own: it runs on your machine, needs no subscription, and works with any agent and any model, local or paid. ## Why Reading is not learning. SLP is built around the loop that turns material into knowledge you can defend: ```loop input a book, course, paper, class, audiobook ↓ read listen, read, understand the material ↓ logic connect it to what you already accept ↓ internalize the concept holds up on its own ↓ externalize write, explain, build · get feedback · again ↓ defend exams, with the topic's own language ``` The first steps are yours. The rest needs someone who explains, points out your mistakes and makes you try again. That is the role of the agent: a teacher that probes your level, grades against a rubric, records what you got wrong and brings it back before you forget it. Knowledge you can't express was never learned. So every step ends in something you produce: notes, answers, exercises, exams. ## Pieces ```repo self-learning-platform/ ├── AGENTS.md # read by any agent ├── agent/ │ ├── skills/ # slp-*/SKILL.md │ ├── agents/ # researcher, teacher-* │ └── reference/ # shared file formats and rules ├── app/ # uv run slp └── topics/ └── example/ ``` Four pieces talk to each other through the filesystem: | path | role | | --- | --- | | `agent/skills/` | the method: teach, build exams, exercises and cards, grade, quiz | | `agent/agents/` | a researcher that verifies, and a teacher per topic | | `topics//` | notes in Markdown, exams and cards in JSON, attempts, a wiki, progress. No database | | `app/` | local notebook, exam simulator and flashcards, with dictation, Mermaid and LaTeX. Tracks your study sessions | ## Layers - **core**: the app, your notes, exams, cards and session tracking. Needs no agent or model, and every file is plain Markdown or JSON you can write by hand - **agent layer** (optional): skills and agents for teaching, grading, feedback, progress and the wiki, on whatever model you plug in Next: [prerequisites](prerequisites.md) · [AI teacher](ai-teacher.md) · [deploy](deploy.md). # Prerequisites Source: https://selflearningplatform.github.io/docs/prerequisites One thing is required. The rest is recommended, not needed. ## Required - **uv**: installs Python (≥ 3.9) and the packages on its own. See [installation](install.md) - **internet, once**: the first `uv run slp` downloads the packages. After that it aims to work offline: Mermaid, KaTeX and the fonts ship in `app/vendor/` ## Recommended | software | why | | --- | --- | | [Claude Code](https://claude.com/claude-code) | the agent the skills are written for. Any agent works, see [slp-setup](slp-setup.md) | | [VS Code](https://code.visualstudio.com/) | to read and edit the topic files, and run the agent in its terminal | | [GitHub](https://github.com/) | to keep your topics in your own private repo | | [git](https://git-scm.com/) | to clone the repo | // the core (the app, notes, cards, exams, exercises) needs no agent at all. The agent layer does # Installation Source: https://selflearningplatform.github.io/docs/install [uv](https://docs.astral.sh/uv/) manages Python and the dependencies. Install it once, then clone. ```macOS · linux curl -LsSf https://astral.sh/uv/install.sh | sh ``` ```windows powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" ``` ```clone git clone https://github.com/GustavoPenaBeltrami/self-learning-platform.git cd self-learning-platform uv run slp ``` - **requires**: python ≥ 3.9, fetched by uv - **deps**: `mlx-whisper` on Apple Silicon, `faster-whisper` elsewhere. Only [dictation](dictation.md) uses them - **keep your topics**: `git remote set-url origin ` // want dictation ready offline? run `uv run slp setup` right after cloning ## Recommended setup Your material, the app and the agent side by side. Two monitors help. ```layout ┌──────────┬──────────┬──────────┐ │ material │ app │ agent │ │ pdf │ notes │ /slp-* │ │ course │ exams │ │ │ │ cards │ │ └──────────┴──────────┴──────────┘ ``` - **1 · material**: the PDF, the course, the docs. Whatever you're studying - **2 · app**: `uv run slp` in the browser: notes, cards, exams, exercises, settings - **3 · agent**: open at the repo root. Teaches, builds exams, grades Next: [getting started](getting-started.md). # Getting started Source: https://selflearningplatform.github.io/docs/getting-started Know what you want before you open the agent. Then two skills: one sets up the agent, one sets up the topic. ## Have this ready | what | example | | --- | --- | | subject | *HTTP basics*, the AWS Cloud Practitioner cert, a novel | | sources | a PDF on disk, a course URL, the official docs | | timeframe | an end date: `2026-12-01` | | frequency | cadence and session length: 3× a week, 45 min | | goal | 1–3 concrete goals, and why: "justify PUT vs POST in a review" | `slp-init` asks for all of it. Vague goals get asked again: they become the topic's mission. ## 1 · Set up the agent Open your agent at the repo root and send: ```agent Read AGENTS.md, then read agent/skills/slp-setup/SKILL.md and follow it. ``` On the first run your agent hasn't loaded the skills yet, so it can't call `/slp-setup`: the prompt points it at the file. It detects which agent it is and exposes `agent/skills` and `agent/agents` in that agent's format: symlinks if it supports them, conversion if not. It also offers optional session hooks, where your agent supports them. Everything it creates goes to `.git/info/exclude`. ```what it runs · claude code mkdir -p .claude ln -s ../agent/skills .claude/skills ln -s ../agent/agents .claude/agents ``` Full reference: [slp-setup](slp-setup.md). ## 2 · Create a topic A topic is anything you study: a book, a certification, a tool's docs, a course. The skills are loaded now, so call it by name. ```agent /slp-init I want to start studying HTTP. ``` The agent interviews you, creates the folder, checks your level with a few questions and offers a dedicated teacher. ```creates topics// ├── topic.json # goals, languages, sources, routine ├── learning.md # mission, glossary, record ├── resources/ notes/ exams/ exercises/ ├── wiki/ │ └── index.md # the agent's map of the subject (OKF) └── progress/ ├── status.md ├── log.md └── sessions.jsonl ``` `topics/example/` is a small one to look at. Your own topics are git-ignored. ## 3 · Study Start the app with `uv run slp`, and each session with `/slp-session`: it shows what's pending and hands off to the right skill. The app records your study sessions on its own. Full reference: [slp-init](slp-init.md) · [skills](skills.md) · [topics/\](topics.md). # AI teacher Source: https://selflearningplatform.github.io/docs/ai-teacher Studying alone fails at the same point: nobody tells you what you got wrong. The agent plays the teacher. It explains from what you already know, asks before it tells, grades against a rubric and writes down your mistakes so the next exercise and exam press on them. It never replaces the work. You read, you answer, you produce. It gives feedback. ## Key terms - **LLM**: a language model. Reads text, writes text. Claude, GPT, Gemini, or a local one via Ollama - **agent**: an LLM that can use tools: read and write files, search the web, run commands. Claude Code, Codex, Gemini CLI, opencode. You talk to it in the third window - **skill**: a Markdown file with instructions for one job, in the Agent Skills format: `name` + `description` frontmatter, steps below. The agent loads it when you call it or when your request matches its description ## What the teacher does | role | skill | | --- | --- | | measures your level | [slp-quiz](slp-quiz.md), [slp-teach](slp-teach.md) | | explains, node by node | [slp-teach](slp-teach.md) | | condenses your material | [slp-summarize](slp-summarize.md) | | builds exams and exercises | [slp-exam](slp-exam.md), [slp-exercises](slp-exercises.md) | | grades and gives feedback | [slp-grade](slp-grade.md) | | turns your notes into flashcards | [slp-cards](slp-cards.md) | | reads your sessions and says what's next | [slp-session](slp-session.md) | | verifies before stating | [researcher](researcher.md) | Everything it writes is plain JSON and Markdown, so you can write any of it by hand: a topic, notes, cards, an exam, an exercise. The skills guide the process. What needs the agent is the judgment: teaching, grading, reading your sessions, keeping the wiki. ## Memory The agent keeps no memory between sessions. The topic folder is the memory: `learning.md` holds what you've shown and what you got wrong, `progress/` holds where you are and every study session, `wiki/` holds the map of the subject. Every skill reads them before writing. ## Wiki Each topic has a `wiki/`: the agent's own map of the subject, an OKF bundle in the llm-wiki pattern. One page per concept with what it depends on and its sources, one page per source, and an index. `slp-teach`, `slp-summarize` and `slp-session` write it; `slp-teach` reads it to plan a lesson, and `slp-exam`, `slp-exercises` and `slp-quiz` draw on it. The only thing it records about you is which sessions studied each concept. The app doesn't show it. Format: [wiki/](wiki.md). ## Agents - **[researcher](researcher.md)**: verifies a fact or maps a topic, with sources. Shared by every topic - **[teacher-\](teacher.md)**: one per topic, offered by `slp-init`. A persona the agent adopts while teaching: who would teach that subject in real life # Skills Source: https://selflearningplatform.github.io/docs/skills Every skill can be called by name, `/slp-teach`. Two are manual only: `slp-setup` and `slp-init` set `disable-model-invocation: true`, so the agent never starts them on its own. The rest can also be started by the agent when your message matches their description. - **manual**: runs only when you call it - **invoked**: runs when you call it or ask for that exact job - **auto**: the agent also starts it without being named, on the triggers below | skill | step | starts | also triggered by | | --- | --- | --- | --- | | [slp-setup](slp-setup.md) | setup | manual | none (first run: point the agent at the file, see [getting started](getting-started.md)) | | [slp-init](slp-init.md) | setup | manual | none | | [slp-session](slp-session.md) | plan | auto | opening a session with no concrete request, "I'm done for today", "I finished chapter X" | | [slp-summarize](slp-summarize.md) | prepare | invoked | pasting a fragment to condense | | [slp-teach](slp-teach.md) | prepare | auto | anything that needs explaining: "explain", "I don't get it" | | [slp-exercises](slp-exercises.md) | practice | auto | `slp-session` recommends one for a finished unit | | [slp-exam](slp-exam.md) | practice | invoked | pasting a chapter and asking for an exam on it | | [slp-grade](slp-grade.md) | feedback | auto | sitting an exam or quiz in the app, submitting an exercise | | [slp-quiz](slp-quiz.md) | control | auto | `slp-init` on a new topic, `slp-session` says a quiz is due | | [slp-cards](slp-cards.md) | review | auto | finishing a unit's notes | ## Hand-offs `slp-session` asks what you want today and calls the right skill. When a topic has a [teacher persona](teacher.md), `slp-teach` teaches as it. ```loop slp-session ─┬─ slp-summarize ├─ slp-teach ───── as teacher- ├─ slp-cards ───── cards tab ├─ slp-exercises ─┐ ├─ slp-exam ──────┼─ slp-grade └─ slp-quiz ──────┘ ``` At the end of a unit, `slp-session` recommends them in order: cards, then an exercise, then an exam. ## Sessions Every skill logs its work as an `activity` line in `progress/sessions.jsonl` when it starts on a topic, opening a session only if none is open. `slp-session` validates what was recorded. Format: [progress/](progress.md). ## Tool names Skills name Claude Code tools. Other agents map them, as `AGENTS.md` explains: | in a skill | means | | --- | --- | | `AskUserQuestion` | ask with options, or a numbered question | | `Agent` | delegate to `agent/agents/X.md`, or follow it yourself | | `WebSearch` | search the web, as `sources_mode` allows | | `Bash` | run a command, or ask you to run it and paste the output | # Session Source: https://selflearningplatform.github.io/docs/slp-session Entry point of a study session. Validates the sessions recorded since the last one, surveys every topic, asks what you want to do today and hands off to the right skill. It also ends a session and marks a unit done. ```usage /slp-session ``` ```output http-basics: the 24/09 practice session was closed by the hook at 19:30; the last change was your attempt at 18:00. Fixed. Wiki updated: idempotency. http-basics 01 · 1 attempt pending · 3 cards due · 1 unsure card 01 — methods and status codes - [x] cards: 6, 2 due → 3 cards tab - [ ] exercise → slp-exercises ← next - [ ] exam → slp-exam # what do you want to do today? ``` - **triggers**: "let's start", "what do I study today", "what's pending", "I'm done for today", "I finished chapter X" - **reads**: `progress/sessions.jsonl`, the files changed in each session, `topic.json`, `status.md`, `log.md`, pending attempts, `cards/` - **writes**: `end` and `correct` lines in `sessions.jsonl`, the `## Reading` table of `log.md`, `status.md` when a unit is done, the wiki from notes you changed, and whatever you accept in step 3 ## Steps 1. validate the recorded sessions 2. survey, without asking 3. complete `topic.json` or add a teacher persona, if needed 4. ask what to do today 5. hand off ## Validate The app records sessions on its own: any activity in a topic opens one, 30 idle minutes close it, and the statusline has a start/stop control. `uv run slp sessions` closes idle sessions and prints the open ones. For each closed session not yet validated, the real end is the latest file changed or activity logged before the next session. It appends a `correct` line with that end and the files produced. The recorded end gets fixed when it is 30+ minutes after the real one, a file changed after it, or it lists no outputs while files changed. Notes you changed feed the [wiki](wiki.md): the concepts they cover get upserted and the session is added to their `studied`. Open sessions are left alone. Format: [progress/](progress.md). ## Survey - **pending attempts**: `attempts/.` without `.feedback.md`. A `draft.json` is not pending - **due cards**: per topic, by the Leitner rule - **unsure cards**: the ones you flagged in the [cards tab](cards-app.md) - **finished units** with something missing: the checklist below - **quiz due**: the last one is 30+ days old, or 2+ units were finished since - **incomplete `topic.json`**: empty `goals` or `reason` - **no teacher persona**: and `topic.json` has no `"teacher": false` ## Hand-offs | option | goes to | | --- | --- | | read on my own | nothing: the app records the reading as you take notes | | summarize material | [slp-summarize](slp-summarize.md) | | get taught something | [slp-teach](slp-teach.md) | | make flashcards from my notes | [slp-cards](slp-cards.md) | | study my cards | the [cards tab](cards-app.md) | | go over my unsure cards | itself: teaches each flagged card, then clears the flag | | do an applied exercise | [slp-exercises](slp-exercises.md) | | sit an exam | an unsat exam for the unit in the app, otherwise [slp-exam](slp-exam.md) | | check my level | [slp-quiz](slp-quiz.md) | | grade a pending attempt | [slp-grade](slp-grade.md) | ## End of a unit When you say a unit is finished, it adds a `## Reading` row to `log.md`, moves `status.md` to the next unit and shows the unit's checklist, in this order, with the first unchecked item as next: | item | done when | otherwise | | --- | --- | --- | | cards | a card has a `note` under the unit's note | `slp-cards` | | exercise | an exercise for the unit has an attempt | `slp-exercises`, or submit it, or `slp-grade` | | exam | an exam for the unit has a graded attempt | `slp-exam`, or sit it, or `slp-grade` | A quiz due goes on one extra line: `quiz: last one 34 days ago → slp-quiz`. # Agent setup Source: https://selflearningplatform.github.io/docs/slp-setup Exposes `agent/skills` and `agent/agents` in the running agent's native format, without touching anything versioned. ```usage Read AGENTS.md, then read agent/skills/slp-setup/SKILL.md and follow it. ``` ```opencode.json · offline { "provider": { "ollama": { "npm": "@ai-sdk/openai-compatible", "options": { "baseURL": "http://localhost:11434/v1" }, "models": { "qwen3-coder:30b": {} } } } } ``` - **manual only**: `disable-model-invocation: true`. On the first run the skills aren't loaded yet, so point the agent at the file; later, `/slp-setup` works - **when**: first time with a new agent, or after editing `agent/` if your agent uses conversion - **reads**: `global.profile` in `settings.json`, the agent's current docs - **hooks**: optional. At start `uv run slp sessions` closes idle sessions; at exit `uv run slp sessions close` ends the open ones, and the next `slp-session` corrects that end if the work stopped earlier - **writes**: symlinks or converted files, the hooks if you accept them (`.claude/settings.local.json` in Claude Code), plus `.git/info/exclude` ## Order of preference 1. nothing: the agent reads `AGENTS.md` 2. symlink to `agent/` 3. conversion, only for what doesn't fit # New topic Source: https://selflearningplatform.github.io/docs/slp-init Registers a new topic so the first session starts with a clear mission and the level already measured. ```usage /slp-init I want to start studying HTTP. ``` ```output topics/http-basics/ ├── topic.json ├── learning.md ├── resources/ notes/ exams/ exercises/ ├── wiki/index.md └── progress/{status.md,log.md,sessions.jsonl} agent/agents/teacher-http-basics.md ``` - **manual only**: `disable-model-invocation: true`. Call `/slp-init`; the agent won't start it on its own - **asks**: title, subtitle, type (book, certification, documentation, course, practice), area, goals, reason, languages, routine, end date, unit list, sources, `sources_mode` - **sources**: offers to copy each local `path` into `resources/`. `sources_mode` defaults from your [profile](profiles.md) - **slug**: kebab-case of the title. Stops if the folder exists - **writes**: `topic.json`, `learning.md` with the Mission, empty `resources/ notes/ exams/ exercises/`, `wiki/index.md`, `progress/`, optional teacher persona - **first quiz**: runs [slp-quiz](slp-quiz.md), which builds the bank in `exams/quiz/` and measures your level in the chat. The edges go to `learning.md` - **teacher**: offers a [persona](teacher.md) for the topic, default yes. Declining writes `"teacher": false` to `topic.json` ## Steps 1. interview 2. generate the filesystem 3. first quiz 4. the topic's teacher persona 5. wrap up # Summarize Source: https://selflearningplatform.github.io/docs/slp-summarize Summarizes a text and appends it to a note, following the format the notes already use, and files the source into the topic's wiki. ```usage /slp-summarize resources/ch-02.pdf into notes/02-caching.md, extensive ``` - **source**: pasted, attached, a file in `resources/`, or a `path` in `links` - **destination**: the note you name or are working on. None fits: a new `notes/NN-.md` - **levels**: `compact` · `medium` (default) · `extensive` - **writes**: appends to `topics//notes/*.md` in `language.notes`, a source page and the concepts it touches in the [wiki](wiki.md), **Summary written** in `status.md` - **after**: asks whether this finished the unit # Teach Source: https://selflearningplatform.github.io/docs/slp-teach Teaches so it ends up understood, not memorized. Probes your real level, plans the topic as a dependency graph and builds it node by node from truths you already accept. ```usage /slp-teach why is PUT idempotent and POST not? ``` ````notes/03-idempotency.md # Idempotency ```mermaid graph TD safe --> idempotent idempotent --> retries ``` ## Why it matters ... ```` - **triggers**: "teach me", "explain", "I don't get it", any explanation - **principles**: unconditional truths first · "how could I have discovered this myself?" - **persona**: when the topic has a [teacher](teacher.md), it teaches as that persona in the same conversation - **reads**: `learning.md`, `wiki/index.md` and the concept pages, the topic's sources - **writes**: a new `notes/NN-title.md`, `learning.md`, one [wiki](wiki.md) concept per node taught, `progress/status.md` - **after**: asks whether the lesson finished the unit; if not, reminds you that [cards](slp-cards.md) turn it into retention ## Process 1. probe, never skipped 2. plan the dependency graph, then wait for your go-ahead 3. teach, node by node # Exercises Source: https://selflearningplatform.github.io/docs/slp-exercises An exam tests whether something was understood. An exercise forces you to use it to produce something new: code, an ADR, a critique, an explanation. ```output exercises/adr-topic-vs-queue/ ├── prompt.md ├── img/ └── attempts/ ├── 2026-09-24T1030.md └── 2026-09-24T1030.feedback.md ``` - **triggers**: "I want an exercise", "apply what I learned", `slp-session` on a finished unit - **aims at**: the latest thing learned, or a misconception you corrected - **formats**: apply · build · critique · teach. Default by the topic's `type`. A build may ask for a visual artifact: a comparative table, a concept map in Excalidraw or on paper, submitted as an image - **reads**: `topic.json`, `status.md`, `learning.md`, the unit's notes and wiki pages, existing exercises - **writes**: `exercises//prompt.md`. You submit in the app's Exercises tab, with images attached, or by hand as `attempts/.` - **graded by**: [slp-grade](slp-grade.md) # Exam Source: https://selflearningplatform.github.io/docs/slp-exam Builds a JSON exam from content you paste, audited first against the topic's progress. You sit it in the app, which shuffles the questions. ```exam.json { "title": "HTTP — Ch 1: Methods", "questions": [ { "type": "multiple_choice", "q": "Which method is idempotent?", "options": ["POST", "PATCH", "PUT", "CONNECT"], "answer": 2, "explanation": "..." }, { "type": "open", "q": "When is 409 better than 400?", "rubric": ["...", "...", "..."] } ] } ``` - **material**: what you paste; otherwise the unit's notes and [wiki](wiki.md) pages; otherwise the topic's sources. Nothing invented - **writes**: `exams/-/exam.json`. An exam with attempts is never replaced: a new one gets `-b` - **types**: asks which go in. Multiple choice and open are preselected, plus oral and practical for `practice` topics - **size**: 10–15 questions: ~30% definitions, ~40% application, ~30% trade-offs. About 60% multiple choice - **after**: sit it in the [app](exam-app.md), then [slp-grade](slp-grade.md) grades the attempt ## Question types | type | carries | | --- | --- | | `multiple_choice` | 4 `options`, `answer` (0-based), `explanation` | | `open` | `rubric`: 3–5 checkable points | | `oral` | like open, answered by voice | | `practical` | `rubric` | All fields, and the attempt format: [exams/](exams.md). # Grade Source: https://selflearningplatform.github.io/docs/slp-grade Checks each rubric or prompt point, says what's missing (not just "it's wrong"), and records it so the next exercise, exam and session can use it. Every attempt goes through it, multiple choice only too: it is what logs the score. ```attempts/2026-09-24T1030.feedback.md # Attempt — 01-methods-and-codes — 2026-09-24 10:30 **MC:** 3/4 · **Rubric points:** 2/3 ## [05] Open — 409 vs 400 ✓ names the conflict with state ✗ no decision criterion. See *Methods and status codes › Status codes* ## What to improve ... ``` - **grades**: exam, exercise and quiz attempts. Oral answers recorded as audio are transcribed first - **pending**: `attempts/<date>.<ext>` without its `<date>.feedback.md`. A `draft.json` is never graded - **images**: an exercise attempt's `![](img/<file>)` images (diagrams, concept maps, photos of paper) are looked at and graded against the prompt. Missing or unreadable = the criterion is not met, and the feedback says so - **facts**: graded against the version you were taught; doubtful ones go to the [researcher](researcher.md) - **writes**: `attempts/<date>.feedback.md`, `learning.md`, a row in `progress/log.md`, `status.md` if the present changed - **after**: the score, the weakest point and one next step: `slp-teach`, `slp-exercises` or `slp-cards` - **date**: always `YYYY-MM-DDTHHmm` # Quiz Source: https://selflearningplatform.github.io/docs/slp-quiz Measures your level in a topic with a ping-pong quiz in the chat: up a level on a hit, down on a miss, per prerequisite thread. It uses the same question bank every time, so today's result compares with the first one. ```usage /slp-quiz where am I on http-basics? ``` ```attempts/2026-09-21T1000.feedback.md # Attempt — quiz — 2026-09-21 10:00 | Thread | Edge level | |--------|------------| | methods | 3 | | status-codes | 1 | ## [05] Multiple choice — overloaded server ✗ Picked 404. ... ``` - **triggers**: `slp-init` on a new topic, `slp-session` when a quiz is due, "check my level", "where am I on this topic" - **bank**: `exams/quiz/exam.json`, built on the first run. 3–6 threads, 1–2 questions at levels 1, 3 and 5, each tagged `thread` and `level`. Questions are never edited or reordered; a thread whose ceiling wasn't found gets higher levels appended - **run**: per thread, starts at level 3 and stops when a hit at one level and a miss at the next bound it. "I don't know" is a miss. No teaching mid-quiz - **edge**: the highest level answered correctly in a thread, `0` if none - **writes**: `exams/quiz/attempts/<date>.json` with `"mode": "chat"`, its `<date>.feedback.md`, a row in `## Quizzes` of `progress/log.md`, and `learning.md` when an edge moved or a misconception showed up - **in the app**: the bank can also be sat as a plain exam, not adaptive. [slp-grade](slp-grade.md) grades that attempt - **due**: `slp-session` suggests one when the last is 30+ days old or 2+ units were finished since ## Steps 1. open: pick the topic, read the Mission, `learning.md` and the wiki index 2. build or extend the bank 3. run the ping-pong 4. write the attempt, feedback, log row and record 5. wrap up: the edge per thread, what moved, one next step # Cards Source: https://selflearningplatform.github.io/docs/slp-cards Turns your notes into flashcards, one concept per card: a question on the front, a short answer on the back. You study them in the [cards tab](cards-app.md), which schedules them with Leitner boxes. This skill only writes the cards. ```usage /slp-cards make cards for unit 01 of http-basics ``` ```cards/cards.json [ { "id": "put-idempotent", "front": "Why is PUT idempotent?", "back": "It replaces the whole resource, so repeating it leaves the same state.", "note": "Methods and status codes" } ] ``` ```output 6 added, 1 changed, 0 removed · 14 total ``` - **triggers**: "make cards", "flashcards from my notes", "update my cards", finishing a unit's notes - **scope**: the whole topic, or one unit. Default: the whole topic - **reads**: only your notes. A fact not in them gets no card - **cards**: `front` asks for recall, not recognition. `back` is at most 2 sentences. 5–15 per note. `note` is the note's H1, which puts the card in that note's set - **updates**: a card whose fact is unchanged stays as is; a changed fact keeps its `id`; a fact gone from the notes loses its card. Ids never change or get reused - **leaves alone**: cards you wrote in the app (`"by": "user"`), the `flagged` mark, custom sets and `cards/reviews.jsonl` - **writes**: `cards/cards.json`. Format: [cards/](cards.md) # Researcher Source: https://selflearningplatform.github.io/docs/researcher Verifies a fact or maps a topic and returns a short report with sources. Used before teaching or grading anything that isn't certain, and to survey a topic before planning a lesson. Works in an isolated context. ```report **Verdict:** correct ## Summary PUT is idempotent (RFC 9110 §9.2.2) ... ## Findings ## Source choice ## Sources ## Gaps ``` - **tools**: WebSearch, WebFetch, Read, Grep, Glob - **source mode**: follows the topic's `sources_mode`: `web`, `local` or `both` - **verdicts**: `correct`, `correct (local only)`, `incorrect`, `unverifiable` - **conflicts**: picks one with confidence; an earlier `Source choice` in `learning.md` decides, otherwise the topic's own material wins unless outdated. The decision comes back as a **Source choice** to copy into `learning.md` - **writes**: nothing. The caller files verified findings into the topic's [wiki](wiki.md) ## Process 1. break the question into 2–4 facets 2. search from different angles 3. read the 2–3 best pages whole 4. synthesize, with sources # Teacher Source: https://selflearningplatform.github.io/docs/teacher A teaching persona, one per topic, offered by [slp-init](slp-init.md). A specialist who would teach this in real life, never a generic "\<area> teacher". ```frontmatter --- name: teacher-http-basics description: Teaching persona for the "http-basics" topic, adopted by the main agent while running slp-teach. Not for delegation. --- You are ... ## How you teach ## What you don't do ``` - **persona, not subagent**: [slp-teach](slp-teach.md) reads the file and the agent teaches as it in the same conversation. A subagent couldn't ask you questions or wait for your go-ahead - **process**: the same as `slp-teach`, with the persona's voice and the topic's typical traps - **memory**: none of its own. It reads the topic's `learning.md` - **rules**: never gives the answer before you try; verifies doubtful facts with the [researcher](researcher.md) - **missing one**: `slp-session` offers to create it. Declining writes `"teacher": false` to `topic.json` so it isn't offered again - **git**: personal and ignored # Profiles Source: https://selflearningplatform.github.io/docs/profiles How the agent layer and dictation run. Chosen at setup, changeable in settings → global. The core is the same in all of them. ```reference stack · offline OLLAMA_CONTEXT_LENGTH=32768 ollama serve # model qwen3-coder:30b # or gpt-oss:20b on 16 GB # agent opencode ``` | profile | behaviour | | --- | --- | | `auto` | default. Online when the server finds a connection at start, offline otherwise | | `online` | a paid agent, web lookups, dictation models downloaded when missing | | `offline` | everything on your machine. Nothing is fetched | - **read by**: [slp-setup](slp-setup.md), to recommend an agent, and [slp-init](slp-init.md), for the default `sources_mode`: `offline` → `local`, `online` → `both`, `auto` → `both` with web access, `local` without // grading and teacher judgment are weaker on local models: treat that output as a draft # Deploy Source: https://selflearningplatform.github.io/docs/deploy Starts the local server and opens the notebook in your browser. If a server is already running, it just opens the page. ```output Profile: online Study app at http://localhost:8321/app/views/notes.html (ctrl+c to stop) ``` - **url**: `http://localhost:8321/app/views/notes.html`, local only (`127.0.0.1`) - **server**: Python stdlib, `app/server/server.py`. No database: it reads and writes `topics/` ## Commands | command | what it does | | --- | --- | | `uv run slp` | start the app | | `uv run slp setup` | pick a profile and download the dictation model ahead of time | | `uv run slp sessions` | close idle study sessions and print the open ones | | `uv run slp sessions close` | close every open study session | | `uv run slp transcribe <audio> [lang]` | print the transcription of an audio file | ## Layout ```screen ┌ 1 notes 2 cards 3 exams 4 exercises 5 settings ── clock ── tools ┐ │ explorer │ │ │ topics/ │ the view │ │ index │ │ └ NORMAL ~/path status · session · zoom % · width px · theme · Top ┘ ``` - **top bar**: the five views, a clock, and the tools of the current view - **explorer**: topics and their contents, per view - **status line**: mode (`NORMAL`, `INSERT`, `PASS`/`FAIL`), path, save status, the session control, content zoom, text width, theme picker and scroll position. Zoom and width are remembered per browser - **session control**: shown when a topic is open. Start or stop a study session by hand; otherwise the app opens one on any activity in the topic and closes it after 30 idle minutes ## Views | view | what it is | | --- | --- | | [notes](editor.md) | list of topics, and the notebook of each one | | [cards](cards-app.md) | card sets per topic, and the flashcard study | | [exams](exam-app.md) | list of exams per topic, and the simulator | | [exercises](exercises-app.md) | prompts and your submitted answers per topic, with feedback | | [settings](settings.md) | global (profile, fonts, dictation model), shortcuts, cards and themes. Also at `/settings` | # Notes Source: https://selflearningplatform.github.io/docs/editor A notebook per topic. What you write is saved to `topics/<slug>/notes/` as Markdown, less than a second after you stop typing. The first screen lists every topic: order, title, type, sections and resources. Sort by name or by last edit, or search with the magnifier. Click a topic to open it. ## The page - **title and subtitle**: editable, saved to `topic.json` - **body**: one long page. Each `#` heading becomes its own file on save: `notes/01-<heading>.md`, `02-…`. Rename or delete a `#` and the files follow - **explorer**: `resources/`, then `notes/`: the headings, numbered `1`, `1.1`, `1.1.1`, folded by default. Click to jump. `+ add` under `notes/` starts a new `#` section at the end - **status line**: `saving…`, then the time and the number of sections ## Toolbar | tool | what it does | | --- | --- | | block | paragraph, heading 1–6, list, numbered list | | font | the reading font of the note body. Saved as `global.font` in `settings.json` | | **B** *I* | bold, italic | | swatches | yellow, green, light blue and pink, plus your own: `+` takes a hex color, right-click one of yours to remove it. Yours are saved as `global.mark_colors` in `settings.json`. Used by marks only | | highlight · underline · strike | marks the selection in the chosen color | | reference | link to a section of the notes. Or type `[[heading]]` | | margin note | a sticky note beside the text, in the theme's accent color. Drag it by its handle | | flashcard | a new card for this topic: the selected text becomes the back and the section's `#` heading its note. Write the front and save. See [cards](cards-app.md) | | image | embedded in the text, or a sticky image note with a caption | | diagram | a Mermaid block | | formula | a LaTeX block | | dictate | speech to text, see [dictation](dictation.md). `ctrl+m` | | ⓘ | the keys of this page. See [shortcuts](shortcuts.md) | ## Marks Click a mark to change its color, add a comment or remove it. A comment shows when you hover the mark. Hover a reference to preview the section it points to. ## Images Paste a screenshot, drop a file or use the image button. Click an image to resize it (20–100%) or delete it. On save, images are moved out of the Markdown into `notes/img/<hash>.<ext>`; the same image pasted twice is stored once, and images no note uses are removed. ## Diagrams and formulas Click a block to edit its source; it repaints as you type. Mermaid and KaTeX ship in `app/vendor/`, no internet needed. They follow the theme. ## Sources The explorer shows the topic's sources under `resources/`: the files in `topics/<slug>/resources/` (with size) and the `links` in `topic.json`, a URL (`↗`) or a local path (`local`). `+ add` uploads files into `resources/` or takes a URL or a `~/path`, saved to `links`. `slp-init` offers to copy local files there. Click a source to read it inside the page: PDFs, images, text files and CSV or TSV as a table (first 2000 rows). `Esc` or `✕` closes it. Other files, URLs and `⌘`/`ctrl` + click open in a new tab. // cards and highlights are stored as HTML inside the .md, so the file still opens in Obsidian or on GitHub. See [notes/](notes.md) # Exam simulator Source: https://selflearningplatform.github.io/docs/exam-app Sit an exam built by [slp-exam](slp-exam.md). The level quiz of [slp-quiz](slp-quiz.md), in `exams/quiz/`, is listed too; a sitting here is logged as a quiz. The first screen lists every topic with its exams and how many attempts are **pending** grading. Click a topic to see its exams with questions, attempts and pending counts; click an exam to sit it. Old `?f=topics/<slug>/exams/<exam>/exam.json` links still work. ## Sitting - **order**: questions are shuffled on every sitting - **multiple choice**: pick one option - **open · practical**: a text box - **oral**: record your answer with the microphone, or answer in writing instead - **progress**: `N/M answered` in the status line - **diagrams and formulas** in questions and options render as in the notes ## Draft Answers are saved as a draft about a second after you change anything (`draft saved` in the status line) and when the page is hidden or closed. Open the exam again and it resumes: picked options, written text (also for oral questions answered in writing) and the question order come back (`draft restored`). Oral audio is not drafted: recordings are lost if you leave. The draft is deleted when you finish the exam. ## Grading `⏎` finishes the exam. Multiple choice is graded right away: a score meter, `✓` on the right option, `✕` on yours, and the explanation. `PASS` from 70%. Open, oral and practical answers show `pending grading`. ## The attempt Every sitting is saved, whatever the score. Answers are stored as they are: no grade inside. ```attempt exams/<exam>/attempts/ ├── 2026-09-24T1030.json # your answers └── 2026-09-24T1030-p3.webm # audio of question 3 ``` Then ask your agent to grade it: [slp-grade](slp-grade.md) writes `2026-09-24T1030.feedback.md` next to it; until then the attempt counts as pending. Fields: [exams/](exams.md). | key | action | | --- | --- | | `⏎` | finish the exam, or retake after grading | | `R` | record an oral answer, again to stop | | `T` | answer an oral question in writing | | `Esc` | back: exam → topic → all topics | `⏎`, `R` and `T` can be changed in [settings](settings.md) → shortcuts. The ⓘ in the top bar lists them. # Exercises Source: https://selflearningplatform.github.io/docs/exercises-app Do the exercises built by [slp-exercises](slp-exercises.md). The first screen lists every topic with its exercises and how many attempts are **pending** grading. Click a topic to see its exercises with format, unit, attempts and pending counts; click an exercise to write your answer. ## Writing - **prompt**: the exercise's `prompt.md`, rendered above the editor, images included - **answer**: a plain-text markdown editor; the status line shows the character count - **images**: paste from the clipboard, drop files on the editor, or attach with the button or `ctrl+i`. Each image is saved in the exercise's `img/` and inserted where the cursor is - **previous attempts**: the header shows how many attempts the exercise has and how many are pending grading ## Draft The answer is saved as a draft about a second after you stop typing (`draft saved` in the status line) and when the page is hidden or closed. Open the exercise again and the text is back (`draft restored`). The draft lives in `draft.json` inside the exercise folder and is deleted when you submit. ## Submitting `⌘⏎` saves the answer as a new markdown file, whatever it says. Empty answers are refused. Image links are checked against `img/`. ```attempt exercises/<exercise>/attempts/ └── 2026-09-24T1030.md # your answer ``` The page then shows the saved answer and `pending grading`: ask your agent to run [slp-grade](slp-grade.md), which writes the feedback next to it. Fields: [exercises/](exercises.md). | key | action | | --- | --- | | `⌘⏎` | submit the answer | | `ctrl+i` | attach an image | | `⏎` | after submitting, write again | | `Esc` | leave the editor, then back: exercise → topic → all topics | `⌘⏎`, `ctrl+i` and `⏎` can be changed in [settings](settings.md) → shortcuts. The ⓘ in the top bar lists them. # Cards Source: https://selflearningplatform.github.io/docs/cards-app Flashcards per topic, with spaced repetition. Cards come from [slp-cards](slp-cards.md), from a selection in the [notes](editor.md), or from the form here. They are saved in `topics/<slug>/cards/`, with every rating: [cards/](cards.md). ## Screens - **all topics**: cards, due, unsure and learned per topic, and per set below it - **a topic** (`?topic=<slug>`): its sets. One per note `#` heading that has cards, then sets you named, then `unsorted` - **a set** (`&set=<set>`): starts on its due cards. `&card=<id>` puts that card first - **explorer**: topics, sets and cards. Click a card to study it now - **list**: every card with its note, step, when it is due (`now`, `later`, `learned`) and `?` if unsure. Click a row to edit it, `✕` to delete it When nothing is due, the page says when the next card is and offers study all. ## Studying **Study due** takes the cards whose time has come; **study all** takes every card in the set or topic. The card shows the front. Answer it to yourself, then reveal: the card flips to the answer, with a link to the note section it came from. Rate it: - **don't know**: the card goes back to step 0 and to the end of this session, so you see it again - **knew it**: one step up - **unsure**: counts as don't know, marks the card `? unsure` and leaves the session. Unsure cards wait for your agent: [slp-session](slp-session.md) offers to go over them Only the first rating of a card in a session counts. Ratings are saved at the end of the session, when you open the list and when you close the page. The last screen shows how many you knew, didn't know and were unsure of, and the answers of the ones you missed. While studying you can also add, edit, skip or delete the current card. ## Scheduling Leitner boxes, counted from your ratings. A card's step is the number of "knew it" in a row since its last "don't know". | step | due | | --- | --- | | 0 | now: a new card, or after "don't know" | | 1 … n | the wait of that step after the last review. Defaults: 1, 3, 5, 10, 20, 40 days | | learned | one more "knew it" after the last step. It stops coming up in study due | The waits are yours to change in [settings](settings.md) → cards, for every topic. Past ratings are kept: new waits only move when each card is next due. ## Writing cards `N` opens the form: set, front, back and note. The set can be a note title or any name; empty means the set of its note, or `unsorted`. The note is the `#` heading the card comes from. `⌘⏎` saves, `Esc` cancels. **New set** (`C`) names a set, a note without cards or any name, and writes its first card. From the notes: select text and press the flashcard button in the toolbar. The selection becomes the back and the note is the section you are in. ## Keys | key | action | | --- | --- | | `⏎` | study due · study again after a session | | `A` | study all | | `C` · `N` | new set, new card | | `L` | list | | `Space` | reveal | | `1` · `2` · `3` | don't know, knew it, unsure | | `E` · `S` · `D` | edit, skip, delete the current card | | `Esc` | back to the card from the answer, or back one screen | Change them in settings → shortcuts. All keys: [shortcuts](shortcuts.md). # Settings Source: https://selflearningplatform.github.io/docs/settings The settings tab lists four sections: **global**, **shortcuts**, **cards** and **themes**. All of them live in one file, `settings.json` at the repo root, saved the moment you change an option. Each page has a breadcrumb (`settings › cards`) to go back, and all but themes a `reset all to defaults` button. File reference: [settings.json](settings-json.md). ## Global | option | what it affects | | --- | --- | | profile | `auto`, `online` or `offline`. How the agent layer and dictation run: whether a missing dictation model is fetched, and which `sources_mode` `slp-init` suggests. `auto` picks `online` when the server finds a connection at start; the page shows which one is active. See [profiles](profiles.md) | | ui_font | font of bars, menus and pages. The note body has its own, in the [notes](editor.md) toolbar | | voice_model | the dictation model. `download` saves the selected size to `models/` so it works offline | | model path | a Whisper model folder you already have (MLX on Apple Silicon, CTranslate2 elsewhere). Used as-is | | upload font | a `.woff2`, `.ttf` or `.otf`. Stored in `fonts/`, then offered in both font pickers | ## Shortcuts The key of every action, grouped by page: notes, cards, exams, exercises. Click a key, press the new combo; `Esc` cancels. A key already used on the same screen is refused. Notes keys need `ctrl`, `alt` or `meta` and can't take one the editor uses (`⌘B`, `⌘C`, `⌘Z`…). Reset one key or all. Fixed keys are shown but can't be changed. See [shortcuts](shortcuts.md). ## Cards How many days a flashcard waits at each step before it is due again. Type each wait as days since the previous step or since the start; the other column follows. `add step` appends one at twice the last wait, `✕` removes one. The defaults are 1, 3, 5, 10, 20 and 40 days. Past the last step a card is learned. The steps apply to every topic. How they are used: [cards](cards-app.md). ## Themes Pick one there or with ◐ in the status line. `auto` uses `kami` when the OS is light, `sumi` otherwise. - **Ink and paper**: `sumi` 墨 (dark) and `kami` 紙 (light) - **Four with color**: `taiyo` 太陽 (sun, sepia, light), `sakura` 桜 (dark and pink), `umi` 海 (sea, dark and blue), `mori` 森 (woods, dark green and brown) - **Your own**: type a name, pick a base color and dark or light, press `generate`. The app builds the 11 shades from background to text in the hue of that color, an accent from the color itself and the seven terminal colors. Change any of the 19 colors, then `save and use`. `edit` loads a theme's colors into the form (all but `sumi` and `kami`); saving a name you already have replaces it. Your own themes can be deleted. Saved under `theme` in [settings.json](settings-json.md) Colors are tokens in `app/styles/style.css`; the design reference is `app/styles/Design.md`. ## Fonts `mono` (JetBrains Mono), `serif` (a system serif) and `inter` are built in, plus any you upload. Uploaded names are lowercased, with dashes for spaces; a name that already exists is refused. // text too small? use the zoom in the status line or your browser's zoom # Dictation Source: https://selflearningplatform.github.io/docs/dictation Dictate notes or answer oral exam questions out loud. Runs locally with Whisper; without it, the mic tells you how to use your OS dictation instead. ```windows arm uv run --python cpython-3.12-windows-x86_64-none slp ``` ```transcribe by hand uv run slp transcribe attempts/2026-09-24T1030-p3.webm en ``` | system | engine · disk | | --- | --- | | macOS Apple Silicon | mlx-whisper (GPU) · ~2.7 GB | | macOS Intel, Linux, Windows x64 | faster-whisper (CPU) · ~0.7 GB | | Windows ARM | faster-whisper, emulated x64 · ~0.7 GB | ## In the notes `ctrl+m` or the mic button starts and stops. The key can be changed in [settings](settings.md) → shortcuts. Grey text is provisional and keeps changing while you talk; every ~15 s the finished part is committed. The language is the topic's `language.notes`. ## In exams Oral answers are recorded, not transcribed live. The audio is saved next to the attempt and [slp-grade](slp-grade.md) transcribes it to grade the content, not pronunciation. ## Models | size | disk | RAM | | --- | --- | --- | | `tiny` | ~75 MB | ~1 GB | | `base` | ~145 MB | ~1 GB | | `small` | ~480 MB | ~2 GB | | `medium` | ~1.5 GB | ~5 GB | | `turbo` | ~1.6 GB | ~6 GB | | `large-v3` | ~3 GB | ~10 GB | - **default**: `turbo` on Apple Silicon, `small` on CPU - **choose**: `voice_model` in [settings](settings.md) → global: a size, or the path to a model you already have - **where it comes from**: `models/` first; if missing, the `online` profile downloads it on first use and `offline` uses a cached copy or falls back to the OS dictation - **`uv run slp setup`**: `auto` and `online` download `turbo`; `offline` shows your RAM and disk, suggests a size and lets you pick one, a path, or none # Keyboard shortcuts Source: https://selflearningplatform.github.io/docs/shortcuts Every page with keys has an ⓘ in the top bar: hover it for the list of that page, with your current keys. Action keys can be changed in settings → shortcuts (saved under [shortcuts](settings-json.md) in `settings.json`); `Esc`, bold/italic, the card form save and menu arrows are fixed. The tables show the defaults. ## Notes | key | action | | --- | --- | | `ctrl+m` | start and stop dictation. On macOS too: `⌘M` minimizes the window before the page sees it | | `⌘B` · `⌘I` | bold, italic (`ctrl` on Windows and Linux). Fixed | | `Esc` | close menus, previews and an open source. Fixed | | `⏎` in a mark's comment | close the menu. Fixed | ## Typing At the start of a line: | type | becomes | | --- | --- | | `/h1` … `/h6` | a heading | | `/p` | a paragraph | | `/list` · `/ul` · `- ` · `* ` | a list | | `/ol` · `1. ` | a numbered list | | `[[heading]]` | a reference to that section, anywhere in a line | ## Exams See [exams](exam-app.md). `R` and `T` act on the oral question you are on, or the first one still open. | key | action | | --- | --- | | `⏎` | finish the exam (grade) | | `R` | record an oral answer, again to stop | | `T` | answer an oral question in writing | | `⏎` after grading | retake | | `Esc` | back to the list. Fixed | ## Exercises See [exercises](exercises-app.md). | key | action | | --- | --- | | `⌘⏎` | submit the answer | | `ctrl+i` | attach an image | | `⏎` after submitting | write again | | `Esc` | leave the editor, then back to the list. Fixed | ## Cards See [cards](cards-app.md). | key | action | | --- | --- | | `⏎` | study due (sets, list) · study again (after a session) | | `A` | study all | | `C` | new set | | `N` | new card | | `L` | list | | `Space` | reveal the answer | | `E` · `S` · `D` | edit, skip, delete the current card | | `1` · `2` · `3` | don't know, knew it, unsure | | `Esc` | back, or back to the card from the answer. Fixed | | `⌘⏎` | save the card form. Fixed | ## Menus `↑` `↓` move through a dropdown, `Esc` closes it. # Topic folder Source: https://selflearningplatform.github.io/docs/topics Everything about a topic lives in one folder, as Markdown and JSON. It reads fine on GitHub, in Obsidian or in any editor, and you can write any of it by hand. The agent is needed for the judgment: teaching, grading, reading your sessions and keeping the wiki. ```tree topics/<slug>/ ├── topic.json ├── learning.md ├── resources/ ├── notes/ │ └── 01-<chapter>.md ├── wiki/ │ ├── index.md │ ├── concepts/ │ └── sources/ ├── exams/ │ └── <exam>/ │ ├── exam.json │ ├── draft.json │ └── attempts/ │ ├── <date>.json │ ├── <date>-p3.webm │ └── <date>.feedback.md ├── exercises/ │ └── <exercise>/ │ ├── prompt.md │ ├── draft.json │ ├── img/ │ └── attempts/ │ ├── <date>.<ext> │ └── <date>.feedback.md ├── cards/ │ ├── cards.json │ └── reviews.jsonl └── progress/ ├── status.md ├── log.md └── sessions.jsonl ``` - **slug**: the folder name, kebab-case of the title: `http-basics` - **resources/**: your material, served as is by the app. Always a source. Skills never edit it. Add files by hand or upload them from the [notes](editor.md) view - **dates**: `YYYY-MM-DD`, never relative. File names and ids use the minute: `2026-09-24T1030` - **git**: `topics/*` is ignored except `topics/example/`, a complete topic to copy from - **reference**: every format, with its rules, is in `agent/reference/formats.md` in the repo # topic.json Source: https://selflearningplatform.github.io/docs/topic-json Identity and preferences of a topic. Written by [slp-init](slp-init.md), completed by [slp-session](slp-session.md). The app edits only `title`, `subtitle` and `links`, and keeps the rest. ```topics/example/topic.json { "title": "Example topic: HTTP basics", "subtitle": "Template showing how a topic is structured", "type": "documentation", "area": "web", "order": 999, "goals": [ "Pick the right method for a request and justify it by safety and idempotency", "Choose the status code a response should carry, by range and meaning" ], "reason": "Design and review small REST APIs without guessing methods and codes.", "language": { "source": "en", "notes": "en", "exams": "en" }, "routine": { "cadence": "", "session": "" }, "end_date": "", "sources_mode": "both", "links": [ { "title": "MDN: Overview of HTTP", "url": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview" }, { "title": "HTTP: The Definitive Guide (my copy)", "path": "~/Books/http-the-definitive-guide.pdf" } ] } ``` | field | meaning | | --- | --- | | `title` | shown in the app. Missing: the folder name | | `subtitle` | the line under the title | | `type` | `book` · `certification` · `documentation` · `course` · `practice`. Changes how exams are written. The app shows `book` when missing | | `area` | kebab-case, shared across topics: `software-architecture` | | `order` | position in the app's lists, shown as the 3-digit `#`: 1–999. Missing: last, shown as `—` | | `goals` | 1–3 concrete goals | | `reason` | what changes once you know it. With `goals`, the mission | | `language` | see below | | `routine` | `cadence` and `session` length, free text | | `end_date` | `YYYY-MM-DD`, when you want to finish | | `sources_mode` | where the agent looks things up: `web` · `local` · `both`. No connection means `local` | | `links` | sources: `{ title, url }` (http or https) or `{ title, path }` to a file anywhere on disk. `resources/` is always a source without being listed | | `teacher` | optional, `false` only: you declined a teacher persona and the agent won't offer it again | ## Language Default languages for what the agent writes. Ask for another in any single request ("give me the exam in English") without touching the file. The agent chats in whatever language you write in. | key | sets the default for | | --- | --- | | `source` | the language of the material | | `notes` | notes, summaries, lessons, and dictation in the notes | | `exams` | exams, exercises and feedback | # learning.md Source: https://selflearningplatform.github.io/docs/learning-md The topic's memory: what you've shown you know, and what you got wrong and corrected. Every skill reads it before writing. ```shape # Learning — <topic> ## Mission ## Glossary ## Record ### 0002 — 2026-09-23 — Mixed up 404 and 503 under load — corrected ### 0001 — 2026-09-22 — Declared prior knowledge: GET and POST ``` - **mission**: why you study this and what is out of scope, from `goals` and `reason` in [topic.json](topic-json.md). A change is confirmed with you and gets a Record entry - **glossary**: terms you can already use, one or two sentences each. Synonyms to avoid go under `_Avoid_` - **record**: entries numbered from `0001`, newest first. One is written only when you demonstrated real understanding, declared prior knowledge, corrected a misconception, moved the Mission, or a source conflict was settled (`Source choice: <claim>`). Covering a topic is not one - **superseded**: an entry is never deleted. A newer one that contradicts it adds `Superseded by NNNN` to the old one The [wiki](wiki.md) maps the subject; this file holds what you know of it. # notes/ Source: https://selflearningplatform.github.io/docs/notes Summaries, lessons and your own notes, numbered. Plain Markdown: it opens in Obsidian, on GitHub or in any editor. ```example notes/ ├── 01-methods-and-status-codes.md ├── 02-caching.md ├── 03-idempotency.md └── img/ └── 3f9a1c0b7e2d4a51.png ``` - **written by**: you in the [app](editor.md), [slp-summarize](slp-summarize.md), [slp-teach](slp-teach.md) - **format**: one `#` per file, topics `##`, subtopics `###`, lists `- ` - **nested lists**: flattened on save. Use a `###` or a second list instead - **tables**: GFM pipe tables, one header row, a `| --- |` row, then rows with no blank lines between them. Links, inline code and code blocks are kept too - **names**: `NN-<slug of the # heading>.md`, `NN` being its position. The app renames the files by this rule on every save - **references**: point to a note by its headings, never by file name: `Methods and status codes › Status codes` - **escaped characters**: a note saved by the app keeps some characters as HTML entities: `*` for a typed `*`, `<` for `<`, `#` for a leading `#`. This stops typed text from turning into markup - **images**: `img/<hash>.<ext>`, managed by the app ## Diagrams and formulas A fenced block is drawn in the app, in notes and in exams. Use it when a drawing says more than prose. ````03-idempotency.md ```mermaid graph TD safe --> idempotent idempotent --> retries ``` ```math p_{99} = \frac{a}{b} ``` ```` - **mermaid**: any [Mermaid](https://mermaid.js.org/) diagram - **math**: one [KaTeX](https://katex.org/) formula per block, display mode ## Marks and margin notes Marks with their color and comment, margin notes with their position, and resized images are kept as HTML inside the Markdown, so they survive the round trip: ```raw html <mark class="mk-highlight" style="--c: var(--ansi-yellow, #d8c06a);" data-comment="...">text</mark> <aside class="card" data-x="768" data-y="120"><div class="card-body">...</div></aside> ``` References are `[[heading]]`. # exams/ Source: https://selflearningplatform.github.io/docs/exams One folder per exam. The folder name is the exam's id: the unit number and title (`01-methods-and-codes`), or `quiz` for the level quiz. Attempts are named by the minute they were made. ```tree exams/01-methods-and-codes/ ├── exam.json ├── draft.json # a sitting in progress, autosaved by the app └── attempts/ ├── 2026-09-24T1030.json # your answers ├── 2026-09-24T1030-p3.webm # oral answer to question 3 └── 2026-09-24T1030.feedback.md # feedback ``` - **written by**: [slp-exam](slp-exam.md) and [slp-quiz](slp-quiz.md) (the exam), the [app](exam-app.md) (attempts), [slp-grade](slp-grade.md) (feedback) - **pending**: an attempt without its `.feedback.md`. `draft.json` is not an attempt: never graded, deleted on submit - **second exam on a unit**: `01-methods-and-codes-b`. An exam with attempts is never rewritten or reordered, since attempts point at questions by position - **same minute**: a second attempt in the same minute gets `-2`, `-3`… ## exam.json ```exam.json { "title": "HTTP — 01: methods and codes", "questions": [ { "type": "multiple_choice", "q": "Which method is idempotent?", "options": ["POST", "PATCH", "PUT", "CONNECT"], "answer": 2, "explanation": "..." }, { "type": "open", "q": "When is 409 better than 400?", "rubric": ["...", "...", "..."] } ] } ``` | field | meaning | | --- | --- | | `title` | shown in the lists. Missing: the folder name | | `type` | `multiple_choice` (default when missing) · `open` · `oral` · `practical` | | `q` | the question | | `options` | multiple choice: exactly 4 statements | | `answer` | multiple choice: index of the right option, 0-based | | `explanation` | multiple choice: why, shown after grading. Required | | `rubric` | open, oral, practical: 3–5 checkable points. No `answer` | `q`, `options` and `explanation` can hold ` ```mermaid ` and ` ```math ` fences, written as `\n` inside the string, with LaTeX backslashes doubled. ## Quiz `exams/quiz/exam.json` is the level check [slp-init](slp-init.md) runs first and [slp-quiz](slp-quiz.md) runs again to compare. A normal exam with three more fields: | field | meaning | | --- | --- | | `kind` | top level, `"quiz"` | | `thread` | per question: the prerequisite line it tests, kebab-case (`methods`) | | `level` | per question: difficulty from 1 to 5 | Questions are never edited or reordered: the bank grows by appending higher levels. The app sits it like any other exam. ## Attempt What you answered, nothing more. No score: multiple choice is scored on screen, the rest by `slp-grade`. ```attempts/2026-09-24T1030.json { "exam": "01-methods-and-codes", "answers": [ { "i": 0, "type": "multiple_choice", "chosen": 2 }, { "i": 1, "type": "open", "text": "..." }, { "i": 3, "type": "oral", "audio": "2026-09-24T1030-p3.webm" } ] } ``` | field | meaning | | --- | --- | | `exam` | the exam folder name | | `i` | position of the question in `exam.json`, not the shuffled order you saw | | `type` | copied from the question | | `chosen` | multiple choice: the option index, `null` if skipped | | `text` | open, practical, or oral answered in writing | | `audio` | oral: file name of the recording next to the attempt (`webm`, `ogg`, `mp4` or `wav`) | | `mode` | `"chat"` when the quiz was run in the chat and written by `slp-quiz` | ## Feedback `attempts/<date>.feedback.md`, written by `slp-grade`, or by `slp-quiz` for a quiz run in the chat. ```attempts/2026-09-24T1030.feedback.md # Attempt — 01-methods-and-codes — 2026-09-24 10:30 **MC:** 3/4 · **Rubric points:** 2/3 ## [03] Multiple choice — overloaded server ✗ Picked 404. … See *Methods and status codes › Status codes*. ## What to improve - … ``` - **sections**: `[NN]` is the question position plus one. Only missed and non-multiple-choice questions get one - **✗**: says what is missing and the note heading that covers it, or that no note covers it yet - **quiz**: the score line is a `| Thread | Edge level |` table # exercises/ Source: https://selflearningplatform.github.io/docs/exercises Applied work. A `prompt.md` from [slp-exercises](slp-exercises.md), your submissions in `attempts/`, and the feedback from [slp-grade](slp-grade.md) next to each one. Submit in the app's Exercises tab, or drop a file with any extension into `attempts/` by hand. ```tree exercises/choose-status-codes/ ├── prompt.md ├── draft.json # work in progress, autosaved by the app ├── img/ │ └── 3f2a9c.png # images attached to attempts └── attempts/ ├── 2026-09-24T1800.md # your submission └── 2026-09-24T1800.feedback.md # feedback ``` - **submission**: `.md` for prose, the language's extension for code. The app writes one `.md` - **images**: shared `img/<hash>.<ext>`, referenced from the attempt as `![](img/<file>)`. The grader looks at each one; a missing or unreadable image fails the criteria it was for - **draft**: `draft.json` is not an attempt: never graded, deleted on submit - **pending**: a submission without its `.feedback.md` - **feedback**: same shape as an [exam's](exams.md), scored as `**Prompt criteria:** met/total` ## prompt.md ```prompt.md # Exercise — Pick the status code **Format:** apply · **Unit:** 01 · **Rests on:** Methods and status codes › Status codes The task, with conditions that can be checked one by one. **Submit:** a `.md` with … ``` | field | meaning | | --- | --- | | `Format` | `apply` · `build` · `critique` · `teach` | | `Unit` | the unit number it belongs to | | `Rests on` | the note headings it builds on | | `Submit` | what to hand in, e.g. "an image of the map attached in the Exercises tab" | The conditions in the task are the grading criteria. # cards/ Source: https://selflearningplatform.github.io/docs/cards Flashcards made from your notes, and every time you rated one. The [cards view](cards-app.md) reads both and works out which cards are due. ```tree cards/ ├── cards.json # the cards └── reviews.jsonl # your ratings ``` - **written by**: [slp-cards](slp-cards.md) or you, by hand or in the app (`cards.json`); only the app (`reviews.jsonl`) - **no state file**: sets and due dates are derived from these two files and `cards` in [settings.json](settings-json.md) ## cards.json ```cards.json [ { "id": "put-idempotent", "front": "Why is PUT idempotent?", "back": "It replaces the whole resource, so repeating it leaves the same state.", "note": "Methods and status codes" }, { "id": "status-503", "front": "What does 503 mean?", "back": "Service Unavailable: the server can't handle the request right now.", "note": "Methods and status codes", "by": "user", "flagged": true } ] ``` | field | meaning | | --- | --- | | `id` | kebab-case, unique in the topic, never changed or reused: reviews join on it. To retire a card, delete it | | `front` | a question | | `back` | the answer, at most 2 sentences. Plain text; inline `**`, `*` and `$…$` LaTeX work | | `note` | the `#` heading of the note it came from, never a `##` or a file name | | `set` | optional: a custom set name, for cards that don't belong to one note | | `by` | optional: `"user"` for a card written in the app. `slp-cards` never edits or deletes those | | `flagged` | optional: `true` when you rated the card **unsure**. Only the app sets it; the agent sets it back to `false` after going over it with you. Editing a card keeps it | **Sets**: a card's set is its `set`, else its `note`, else `unsorted`. The app shows one set per note with cards, in note order, then the custom sets by name, then `unsorted`. ## reviews.jsonl One line per rating, append-only. ```reviews.jsonl {"at":"2026-09-26T10:04","id":"put-idempotent","recall":"good"} ``` | field | meaning | | --- | --- | | `at` | local time, `YYYY-MM-DDTHH:MM` | | `id` | the card. Lines for a card no longer in `cards.json` are ignored | | `recall` | `again` (don't know) or `good` (knew it), rated by you after revealing the back | ## Scheduling Leitner boxes. A card's streak is its `good` ratings since the last `again`. With the steps in `cards.intervals` (default `[1, 3, 5, 10, 20, 40]` days, the same for every topic): - **box 0**: a new card, or one you just missed. Due now - **box 1 to n**: waits the matching step, in days, after its last review - **learned**: one more `good` after the last step. Never due again, until an `again` sends it back to box 0 # wiki/ Source: https://selflearningplatform.github.io/docs/wiki The agent's own map of the subject: its concepts, how they depend on each other, the sources behind them, and in which sessions you studied each one. It follows the [Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) (OKF) and the [LLM wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) pattern: Markdown pages with YAML frontmatter, written and kept current by the agent. The app doesn't show it; it opens fine in Obsidian or on GitHub. ```tree wiki/ ├── index.md # catalog, one line per page ├── concepts/ │ └── idempotency.md # one idea per page └── sources/ └── mdn-http-overview.md # one per resource, link or chapter ``` - **not your notes**: [notes/](notes.md) is your writing and [learning.md](learning-md.md) what you've shown you know. The wiki holds no scores and no "you know X" - **language**: written in `language.notes` of [topic.json](topic-json.md); frontmatter keys stay in English ## Page ```concepts/idempotency.md --- type: Concept title: Idempotency description: A request that leaves the server in the same state whether sent once or many times. depends_on: [/concepts/safe-method.md] studied: [2026-09-22T1700, 2026-09-26T1010] generated: { by: slp-teach/<model>, at: 2026-09-22T18:30:00Z } sources: - id: mdn resource: https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview title: "MDN: Overview of HTTP" --- # Definition N identical requests leave the server as one does.[^mdn] # Common confusions Idempotent doesn't mean "same response". [^mdn]: MDN: Overview of HTTP ``` | field | meaning | | --- | --- | | `type` | `Concept` or `Source` | | `title` | the page name | | `description` | one sentence, repeated in the index | | `depends_on` | Concepts: pages this one builds on. May be empty | | `studied` | Concepts: ids of the [sessions](progress.md) in which you studied it, oldest first. Empty or missing: not studied yet | | `generated` | the skill and model that last edited it, and when, in UTC | | `sources` | `{ id, resource, title }` per source cited. `resource` is a URL, or a path relative to the page for `resources/` files | | `tags`, `status` | optional. `status: draft`, or `deprecated` to retire a page | - **citations**: each sourced claim has a footnote keyed by a `sources` id - **links**: relative to the wiki folder (`/concepts/x.md`). A link to a page not written yet is fine - **identity**: the path. A page is never renamed; it is retired with `status: deprecated` - **common confusions**: the one section drawn from you, and it names no one - **source conflicts**: the page states the version chosen; the decision goes to the `learning.md` Record ## index.md No frontmatter except the format version. Updated in the same turn as every page write. ```index.md --- okf_version: "0.2" --- # Concepts * [Idempotency](concepts/idempotency.md) - A request that leaves the server in the same state whether sent once or many times. # Sources * [MDN: Overview of HTTP](sources/mdn-http-overview.md) - What HTTP is, its message shape and its methods. ``` ## Who writes it | writer | when | | --- | --- | | [slp-init](slp-init.md) | creates `index.md` with empty sections | | [slp-teach](slp-teach.md) | end of a lesson: one Concept per idea taught, the session added to `studied` | | [slp-summarize](slp-summarize.md) | after a summary: one Source page and the Concepts it touches | | [slp-session](slp-session.md) | closing or validating a session with reading: the Concepts in the notes you changed | | [researcher](researcher.md) | its verified findings, filed by the skill that called it into the matching Concept | Read by `slp-session` for progress, and by `slp-teach`, `slp-exam`, `slp-exercises` and `slp-quiz`. # progress/ Source: https://selflearningplatform.github.io/docs/progress Where you are, what you did, and when you studied. ```tree progress/ ├── status.md # the present ├── log.md # results, one row per event └── sessions.jsonl # study sessions ``` ## status.md The present: current unit, whether its summary is written, the next exam, what comes next. Rewritten, not accumulated. ```status.md # Status — HTTP basics **Updated:** 2026-09-22 - **Current unit:** 01 — methods and status codes - **Summary written:** yes - **Next exam:** — ## Up next - [ ] … ``` ## log.md The history of results, in four tables. Rows are appended at the end and never deleted. ```log.md ## Exams | Date | Exam | MC | Rubric | Attempt | |------|------|----|--------|---------| ``` | table | written by | when | | --- | --- | --- | | Reading | [slp-session](slp-session.md) | you confirm a unit is finished | | Exams, Exercises | [slp-grade](slp-grade.md) | after grading. Also fills the unit's score in Reading | | Quizzes | [slp-quiz](slp-quiz.md), or `slp-grade` for a quiz sat in the app | after a run | `Attempt` is the feedback path relative to the exam or exercise folder: `attempts/2026-09-23T1030.feedback.md`. ## sessions.jsonl Your study sessions. A session is a block of time with a list of activities. One JSON object per line, append-only. ```sessions.jsonl {"id":"2026-09-26T1010","event":"start","at":"2026-09-26T10:10","by":"app"} {"id":"2026-09-26T1010","event":"activity","at":"2026-09-26T10:12","kind":"reading","by":"app","detail":"notes/01-methods-and-status-codes.md"} {"id":"2026-09-26T1010","event":"activity","at":"2026-09-26T10:40","kind":"teaching","by":"agent","detail":"idempotency keys"} {"id":"2026-09-26T1010","event":"end","at":"2026-09-26T11:02","by":"hook"} {"id":"2026-09-26T1010","event":"correct","at":"2026-09-26T18:00","by":"agent","end_at":"2026-09-26T10:41","outputs":["notes/02-idempotency-keys.md"],"note":"hook closed at 11:02 but last activity 10:40"} ``` | field | meaning | | --- | --- | | `id` | the start minute, `YYYY-MM-DDTHHmm`. Every line of the session repeats it | | `event` | `start` · `activity` · `end` · `correct` | | `at` | when, `YYYY-MM-DDTHH:MM` | | `by` | `app` · `agent` · `hook` · `idle` | | `kind` | activity: `reading` · `teaching` · `summary` · `cards` · `practice` · `exam` · `quiz` · `feedback` | | `detail` | activity: one line, such as a file, an exam, a concept or a count | | `outputs` | end or correct: files made, relative to the topic | | `note` | end or correct: one line | | `end_at` | correct: the real end time | - **open**: a `start` with no `end`. At most one per topic - **idle close**: an open session whose last line is more than 30 minutes old is closed with an `end` by `idle`, at the time of that last line, before anything else is written - **correct**: the agent's fix after checking a session. Its `end_at`, `outputs` and `note` replace the end's. A session with a `correct` line, or an `end` by `agent`, counts as validated | writer | what | | --- | --- | | app | opens a session on any activity when none is open. Logs `reading` (notes saved, at most once per 10 minutes; a source added), `cards` (`N reviewed, M again`), `exam` and `quiz` (attempt saved). The session control in the status line writes `start` and `end` | | skills | one `activity` when they work on a topic, opening a session by `agent` only if none is open | | [slp-session](slp-session.md) | `end` when you say you're done, `correct` when validating | | `uv run slp sessions close` | `end` by `hook` for every open session, from your agent's end-of-session hook. Without `close` it lists open sessions and applies the idle close | # settings.json Source: https://selflearningplatform.github.io/docs/settings-json Your app preferences, at the repo root, in one file with four sections. Written by the [settings](settings.md) views and by `uv run slp setup`. Git-ignored. A missing file, section or key means the default. ```settings.json { "global": { "profile": "auto", "ui_font": "mono", "font": "mono", "voice_model": "", "mark_colors": "" }, "shortcuts": { "cards.skip": "k" }, "theme": { "active": "", "custom": [] }, "cards": { "intervals": [1, 3, 5, 10, 20, 40] } } ``` Older installs kept `global` flat at the top level, with `shortcuts.json` and `card-config.json` next to it: they are read from there and merged into this file on the next save, and the old files are removed. ## global | field | values | default | | --- | --- | --- | | `profile` | `auto` · `online` · `offline`. See [profiles](profiles.md) | `auto` | | `ui_font` | font of the app: `mono` · `serif` · `inter` · an uploaded file name | `mono` | | `font` | reading font of the note body, same choices | `mono` | | `voice_model` | `""` (default size) · a size (`tiny` … `large-v3`) · a model folder path | `""` | | `mark_colors` | your extra mark colors from the [notes](editor.md) toolbar, `#rrggbb` joined by commas | `""` | - **values**: all strings. Anything else is refused - **read by**: the server, and `slp-setup` and `slp-init` for `profile` - **env**: `NOTES_VOICE_MODEL` sets the model when `voice_model` is empty - **not here**: content zoom and text width live in the browser's local storage ## shortcuts Your rebound keys, written by the [shortcuts](shortcuts.md) view. Only the keys you changed, action → key: ```shortcuts "shortcuts": { "cards.skip": "k", "notes.dictate": "alt+d" } ``` A key is `[ctrl+][alt+][shift+][meta+]` then a letter, digit, `space` or `enter`. Unknown actions, bad keys and keys used twice on one screen are refused; a hand-edited section with any of those reads as the defaults. Notes keys need `ctrl`, `alt` or `meta`, and `ctrl` or `meta` with `a b c i s v x y z` is kept for the editor. ## theme The active theme and your own ones, written by the [themes](settings.md) view and the ◐ selector in the statusline. ```theme "theme": { "active": "sakura", "custom": [{ "name": "my-theme", "dark": true, "colors": { "void": "#09110c", "…": "…" } }] } ``` - **active**: `""` (follows the OS) · `sumi` · `kami` · `taiyo` · `sakura` · `umi` · `mori` · the name of a custom theme. Unknown reads as `""` - **custom**: each theme has a `name` (lowercase letters, digits and dashes, up to 24, not a built-in name), `dark` (`true` or `false`) and `colors` with exactly `void`, `carbon`, `graphite`, `iron`, `slate`, `pewter`, `steel`, `ash`, `fog`, `chalk`, `paper`, `accent`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `orange`, each a lowercase `#rrggbb`. A hand-edited list breaking those rules reads as empty - Older files with `theme` under `global` are still read and moved here on the next save ## cards How long a flashcard waits between reviews, written by the cards view of settings, where you type each step as days since the previous step or since the start and the other column is worked out. The file stores days since the previous step: ```cards "cards": { "intervals": [1, 3, 5, 10, 20, 40] } ``` Those are the defaults. A new card or a "don't know" is due now; each "knew it" in a row moves the card one step; one more "knew it" after the last step makes it **learned** and it stops coming up in "study due". 1 to 20 steps, whole days from 1 to 3650. A hand-edited section breaking those rules reads as the defaults. Your past reviews are kept: changing the steps only changes when each card is next due. ## Other local folders Also at the root, git-ignored, created on demand: | path | holds | | --- | --- | | `models/` | dictation models downloaded from settings or `slp setup` | | `fonts/` | uploaded fonts | | `local/` | your own scratch space. The agent doesn't write there unless asked | # agent/ Source: https://selflearningplatform.github.io/docs/agent-dir The single source of the agent layer. Skills use the Agent Skills format: `name` + `description` frontmatter, instructions below. Agents without skill support read the matching `SKILL.md` by hand, as `AGENTS.md` explains. ```tree agent/ ├── skills/ │ ├── slp-session/SKILL.md │ ├── slp-setup/SKILL.md │ ├── slp-init/SKILL.md │ ├── slp-summarize/SKILL.md │ ├── slp-teach/ │ │ ├── SKILL.md │ │ └── references/philosophy.md │ ├── slp-exercises/SKILL.md │ ├── slp-exam/SKILL.md │ ├── slp-grade/SKILL.md │ ├── slp-quiz/SKILL.md │ └── slp-cards/SKILL.md ├── agents/ │ ├── researcher.md │ └── teacher-<slug>.md # ignored └── reference/ ├── formats.md ├── graded-questions.md └── sources.md ``` - **skills/**: edit them here, never in a copy inside an agent's own folder. [slp-setup](slp-setup.md) links or converts them for your agent - **reference/**: what several skills share, so no skill restates it. `formats.md` is every file under `topics/<slug>/`, `graded-questions.md` the rules for questions with a right answer, `sources.md` which sources count and how to settle a conflict between them Skill list and tool-name mapping: [skills](skills.md). Agents: [researcher](researcher.md), [teacher-\<slug>](teacher.md). # Contributing Source: https://selflearningplatform.github.io/docs/contributing Contributions are welcome. Skills are edited in `agent/skills/`, never inside an agent's own folder. File formats live in one place, `agent/reference/formats.md`: a change to a format goes there and to `topics/example/`. ```shell git checkout -b my-change # edit agent/skills/... or app/... uv run python app/tests/run.py git push origin my-change ``` - **app/server/**: `server.py` (HTTP and the `slp` commands), `store.py` (every file read and write), `voice.py` (dictation), `text.py` (Markdown ⇄ HTML) - **app/views/**: one HTML page per view. Design reference: `app/styles/Design.md` - **tests**: plain scripts in `app/tests/`, no framework. One runs with `uv run python app/tests/test_<name>.py` and prints `ok`; `run.py` runs them all For bugs or ideas, [open an issue](https://github.com/GustavoPenaBeltrami/self-learning-platform/issues/new). Licensed [MIT](https://github.com/GustavoPenaBeltrami/self-learning-platform/blob/main/LICENSE). Thanks to [amosblomqvist/learn](https://github.com/amosblomqvist/learn) for the method behind slp-teach and to [Matt Pocock](https://github.com/mattpocock) for per-topic memory and spaced review. # Roadmap Source: https://selflearningplatform.github.io/docs/roadmap Planned, not built. Nothing on this page works today. - **other agents**: test `slp-setup` on Codex, Gemini CLI, Antigravity and Cline - **Windows ARM**: native dictation - **nested lists**: keep them when a note is saved. Today the app flattens them Ideas and bugs: [open issues](https://github.com/GustavoPenaBeltrami/self-learning-platform/issues).