Self learn config
Skill secondfry/claude-code-self-learn/skills/self-learn-config
Use this when the user wants to change how the self-learn out-of-band loop behaves — the model/gateway the headless learner agents run on, processing thresholds (idle time, batch sizes, per-pass cap), toggling fact/skill extraction on or off, or the claude binary path. Guides edits to ~/.claude/self-learn/config.json so the model id stays coupled to the gateway it's addressed to and the auth token is never stored in the file.From its SKILL.md
npx -y skills add secondfry/claude-code-self-learn --skill self-learn-configAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.7 KB, as published. Nobody here has run it
Editing the self-learn dispatcher config
When this applies
The user wants to tune the out-of-band self-learn loop: change the model the fact-extractor / skill-distiller headless runs use, point them at a different gateway, raise/lower the idle-gate or batch thresholds, disable fact or skill extraction, or pin the claude binary. All of these live in ONE file:
~/.claude/self-learn/config.json
The file is optional — every key has a built-in default. Edit it by hand
(wipe or omit any key to revert to its default). Changes take effect on the
next 30-min launchd pass — no launchctl reload, no plugin reinstall.
Schema
Source of truth: pipeline/lib/config.py (the _SETTINGS table). Every key can
also be set by an SL_* env var. Precedence: env var > config.json > default
— so an env var (e.g. in a one-off shell or the launchd plist) wins over the
file, and the file wins over the built-in default.
| key | env var | type | default | meaning |
|---|---|---|---|---|
idle_min | SL_IDLE_MIN | int | 30 | Minutes a finished transcript must be quiet before processing (idle-gate). |
batch_msgs | SL_BATCH_MSGS | int | 1 | Min NEW real-user messages since watermark to look for facts (1 = any). |
distill_threshold | SL_DISTILL_THRESHOLD | int | 1 | Min NEW tool calls to look for a skill. |
min_file_edits | SL_MIN_FILE_EDITS | int | 1 | Min file edits in those calls to distill a skill. |
agent_timeout | SL_AGENT_TIMEOUT | int | 600 | Per-headless-run wall-clock ceiling, seconds. |
max_per_pass | SL_MAX_PER_PASS | int | 8 | Cap transcripts handed to learners per pass (backlog drains gradually). |
learn_model | SL_LEARN_MODEL | str | claude-sonnet-4-6 | Model id passed via --model. Format depends on learn_gateway — see below. |
claude_bin | SL_CLAUDE_BIN | str | "" | Explicit claude binary path. Empty = auto-resolve (PATH, then fnm default). |
facts_enabled | SL_FACT_NUDGE | bool | true | false disables fact extraction entirely. |
skills_enabled | SL_SKILL_NUDGE | bool | true | false disables skill distillation entirely. |
dispatch_enabled | SL_DISPATCH | bool | true | Master switch — false makes the dispatcher a no-op. |
learn_gateway | SL_LEARN_GATEWAY | str | "" | ANTHROPIC_BASE_URL for the headless runs only. Empty = use whatever claude loads from settings.json. |
learn_token_env | SL_LEARN_TOKEN_ENV | str | "" | Name of the env var holding the upstream gateway's auth token. Empty = don't inject. |
stale_after_days | SL_STALE_AFTER_DAYS | int | 30 | Curator: idle days → mark an agent skill stale. |
archive_after_days | SL_ARCHIVE_AFTER_DAYS | int | 90 | Curator: idle days → archive (×2 if use_count ≥ 3). |
curate_interval_days | SL_CURATE_INTERVAL_DAYS | int | 7 | Curator: min days between SessionStart auto-curate passes. |
curate_min_skills | SL_CURATE_MIN_SKILLS | int | 5 | Curator: min agent-distilled skills before auto-curate acts. |
Booleans accept true/false (JSON) or 1/0 (env). Ints parse loosely.
Model ↔ gateway coupling (the part that bites)
The model id format is gateway-specific. They MUST be set together, or the headless run will send a perfectly-valid id to a gateway that doesn't recognise it and fail:
https://your-gateway.example.com/raw/anthropic→ bare id:claude-sonnet-4-6https://your-gateway.example.com/raw/openrouter→ vendor-prefixed:anthropic/claude-sonnet-4-6
So a correct openrouter config looks like:
{
"learn_gateway": "https://your-gateway.example.com/raw/openrouter",
"learn_model": "anthropic/claude-sonnet-4-6",
"learn_token_env": "SL_LEARN_TOKEN"
}
…and a correct /raw/anthropic config keeps the bare id:
{
"learn_gateway": "https://your-gateway.example.com/raw/anthropic",
"learn_model": "claude-sonnet-4-6",
"learn_token_env": "SL_LEARN_TOKEN"
}
Leaving learn_gateway empty means the headless claude -p uses the same
gateway the interactive session does (from ~/.claude/settings.json's env
block). If that is a local/proxy gateway that rewrites model aliases to some
fixed backing model, an empty gateway + a specific learn_model may be
silently rewritten. If you want a specific real upstream model, set
learn_gateway explicitly to bypass the local gateway for learner traffic only
(interactive sessions are untouched).
Token discipline — never store the token in config.json
config.json may be shared, screenshotted, or read by other agents. The auth
token for the upstream gateway must NEVER be written into it. Instead:
- Put the token in an environment variable (e.g. export it in the shell that
bootstraps the launchd agent, or in
~/.zshrc/ a private file the user sources — NOT in the plugin repo, NOT in~/.claude/settings.json's trackedenvblock unless the user accepts that surface). - Set
learn_token_envto the NAME of that variable, e.g."SL_LEARN_TOKEN".
At pass time the dispatcher reads os.environ["SL_LEARN_TOKEN"] and writes
ONLY {ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN} into a 0600 file at
~/.claude/self-learn/learner-settings.json, passed to the headless runs via
claude -p --settings <that file>. Why --settings and not subprocess env:
~/.claude/settings.json's env block is applied on top of the parent process
env at startup, so it would silently clobber ANTHROPIC_BASE_URL back to the
local gateway — --settings is CLI-arg-scope (tier 2, above user settings) and
is the only thing that actually overrides it. The file is unlinked at the end
of the pass. Its trust surface is the same as ~/.claude/settings.json itself
(which already holds an auth token literally); it is never in the repo and never
in config.json.
If learn_gateway is set but learn_token_env is empty (or the named var is
unset), the dispatcher sends no ANTHROPIC_AUTH_TOKEN — the run will fall back
to whatever auth the gateway itself provides, and may fail closed.
Editing checklist
When the user asks to change a setting:
- Read the current
~/.claude/self-learn/config.json(create it if absent — start from{}and add only the keys you're changing; absent keys use defaults). - Make the edit. For ANY model change, check
learn_gatewaytoo — flip them as a pair per the table above. - Never write a literal token. Use
learn_token_env. - Confirm the file is valid JSON before finishing (
python3 -m json.toolon it). - Tell the user it takes effect on the next 30-min pass — no reload needed.
Verifying the effective config
To see what the dispatcher will actually use this pass (after env > file > default resolution), from the plugin root:
python3 -c "import sys; sys.path.insert(0,'pipeline/lib'); import config; \
import json; print(json.dumps(config.load_all(), indent=2))"