Claude Learner

Configuration

Every setting Learner reads, where to put it, and how to make it stop.

Two layers, read in this order:

  1. $CLAUDE_CONFIG_DIR/learner.json (default ~/.claude/learner.json) — your defaults for every repository. Written by the installer, edited by learner config ….
  2. <repo>/.claude/learner.local.json — optional, gitignored, a partial override for one repository. Written by learner off, learner on and learner config project ….

The project layer wins key by key, and arrays are replaced wholesale rather than merged: a project untrackGlobs is the whole list for that repo, not an addition to the global one. A key you did not set keeps its global value, and "enabled": false survives the merge — it is a deliberate override, not an absent key.

KeyValuesDefaultEffect
levelD/J/C/S/E— requiredHow hard a question is. No valid level means no questions
enabledbooltrueMaster switch for the automatic quiz
questionStyles"auto", or a subset of code/architecture/fill"auto"Which formats a question may take
synthesisFrequencyoff/rare/normal/oftennormalA synthesis question every 0 / 8 / 4 / 2 questions
blanksPerExerciseint ≥ 12// LEARNER-TODO holes cut in a fill exercise
untrackGlobsarray of globs[]Extra paths excluded from quiz material
disabledPathsarray of path prefixes[]Repositories where Learner stays silent
coachboolfalseCoach regime: the dev writes, Claude challenges
coachPollSecondsint ≥ 530How often the watcher measures the dev's changes
coachQuietPollsint ≥ 11Consecutive unchanged polls — a pause — before a review fires
coachMinLinesint ≥ 110Fewer changed lines than this never triggers a review
coachCooldownMinutesint ≥ 03Floor between two reviews
coachMaxWaitMinutesint ≥ 015Emit even without a pause once material has waited this long; 0 disables
coachIdleMinutesint ≥ 145Zero changes for this long — the watcher stops
pilotEnabledboolfalseOpt-in master switch
pilotCadenceDaysint ≥ 17Days between briefs
pilotJudgeIntervalHoursint ≥ 124Minimum gap between queue drains
pilotNudgebooltrueWhether a live manoeuvre may speak on UserPromptSubmit
agentSalvobooltrueQuestions while a subagent is working
agentSalvoQuestionsint ≥ 02Questions in a salvo, before the exercise
agentSalvoFillbooltrueCut a fill-in exercise at the end of a salvo

The seven keys above configure the automatic quiz. The seven coach* keys configure the coach regime, where the dev writes the code and Claude challenges it: a review fires when the dev pauses typing, sized by how much changed since the last one. See the coach section of Usage for how the cadence behaves. The four pilot* keys configure Pilot, an opt-in subsystem that measures delegation to Claude across all repositories over weeks.

level is the only one of the seven with no default. The other six have theirs in LEARNER_DEFAULTS at the top of hooks/learner-config.sh; level is left out on purpose, so a half-finished install asks nothing rather than guessing how much you know. SessionStart mentions it once when that happens. A full config looks like this:

{
  "level": "C",
  "enabled": true,
  "questionStyles": "auto",
  "synthesisFrequency": "normal",
  "blanksPerExercise": 2,
  "untrackGlobs": ["*.md", "*.json"],
  "disabledPaths": []
}

You do not have to edit it by hand: learner config level=S validates and writes for you, and learner config project synthesisFrequency=often does the same for the current repository only. The same shape ships in the repository as learner.json.example, for reference or copy-paste.

Levels

The canonical value is the letter. The full word is accepted too, in any case, so senior, Senior and S are the same setting.

LetterNameWhat a question targets
DDiscoveringsyntax, what a block is for, basic vocabulary
JJuniorwhat the function does, where the code lives
CCompetentwhy this split, edge cases, error handling
SSeniortrade-offs, rejected alternatives, impact on performance and coupling
EExpertinvariants, failure modes, what breaks at scale

The level is a claim about you on this code, not a job title, and it is one word to change. Set it high and the easy questions stop; set it low for a stack you have never touched. Anything that is not one of the five letters or names is not a level at all: the hook treats it as unset and asks nothing.

Turning it off

Three ways, by scope. The first two leave the guardrail in place; the third silences that too, which is rather the point of it.

  1. Everywhere. Set "enabled": false in the global config, or say learner config enabled=false. An explicit learner quiz still runs when you ask for one — you asked.
  2. One repository you own. Say learner off in it. That writes <repo>/.claude/learner.local.json and adds the file to the repo's .gitignore if it is not already there. learner on reverses it. That file is the only thing Learner ever adds to a repository, and only on request — a fill exercise (see above) is a different kind of change: it temporarily edits one of your own source files rather than adding a new one.
  3. One repository you do not own, where nothing of yours should be committed. Add its path to disabledPaths in the global config — learner config disabledPaths='["/path/to/repo"]'. Nothing is written into that repository at all. Matching is a path-prefix check on whole components, so disabling /a/b silences /a/b/c but not a sibling /a/bee; a leading ~/ is expanded, and an entry that exists on disk is resolved to its physical path first, so an entry that reaches the repo through a symlink still matches.

Next: Safety — what a fill exercise does to your files.