Install
openclaw skills install @ryanwaits/recallitBuild and drill spaced-repetition packs with recallit — turn a PDF, URL, repo, or plain concept into honest, source-grounded flashcards, then run the daily review loop. Use when the user wants to "make a deck/pack", "turn this into flashcards", "study X with recallit", or wants to drill/review a pack they already have. Fully keyless (no API keys needed); works entirely through the CLI.
openclaw skills install @ryanwaits/recallitrecallit is a local, code-graded spaced-repetition engine. Everything below runs through the
recallit CLI — no browser required. That matters here: inside a container/remote OpenClaw
install the browser SPA (recallit start's web UI) is usually not reachable, so this skill
is written entirely for the CLI/agent-loop path. It works with zero API keys — ANTHROPIC_API_KEY
is optional and only used by recallit's own built-in pack-authoring agent (recallit pack <source>), which this skill does not depend on.
You do two things with this skill:
If openclaw skills info recallit (or skills check) still shows this skill as "needs
setup," don't wait on OpenClaw to fix it — the install spec above is not auto-run for workspace
skills. Verified directly: openclaw onboard --non-interactive ... completes its "skills
setup" step (skipSkills: false) without touching this skill's missing bun/recallit
binaries — that auto-install path is reserved for trusted bundled skills, not this one. You (the
agent) should just run the installers yourself via your shell tool:
brew install bun # only needed if `bun` itself is missing
bun add -g @waits/recallit # recallit is bun-only (imports bun:sqlite) — install with bun, not npm
On the Umbrel OpenClaw container specifically: verified its image (node:22-trixie-slim) ships
neither bun nor recallit by default, only node + brew + chromium, and apt/apt-get are
shimmed to fail and point at brew — so brew install bun is the right first command there, not
apt. Confirmed ~/.recallit resolves to /data/.recallit in that container (HOME=/data),
which is the volume the image already persists across app updates — no extra data-dir
configuration needed.
You never grade the user's answer. You ask the question, take the user's typed reply, and hand it to the CLI:
recallit answer <cardId> "<user's exact answer>" --topic <id>
This calls the engine's gradeResponse function and prints the verdict, e.g.:
answer="Scale a new habit down so it takes less than two minutes to do" -> Good (matches after normalization)
next due 2026-07-09T00:00:00.000Z
Report exactly that rating and reason back to the user. Do not soften it, override it, or decide yourself whether the answer was "close enough" — the CLI already decided.
recallit answer grades differently depending on how the card was authored (see "draft cards"
below) — the same command works for both, dispatch is automatic:
meta.grader) use lexical similarity against back verbatim — verified
directly: a correct paraphrase ("make the new habit take under two minutes to start" for a card
whose back was "Scale a new habit down so it takes less than two minutes to do") scored 0.48
similarity and came back Again, below the pass threshold. Nudge the user toward the card's key
wording for these — don't imply any correct-in-spirit answer will pass.meta.grader: "coverage" + meta.rubric) grade for meaning: an LLM
examiner judges whether the answer demonstrates each required checkpoint, but code re-verifies
the cited evidence is a literal substring of the user's own answer before it counts — the
rating is still 100% code-decided, only the "did they get the idea" read is semantic. This path
needs a model: ANTHROPIC_API_KEY (default), or any OpenAI-compatible endpoint via
RECALLIT_EXAMINER_URL + RECALLIT_EXAMINER_MODEL (+ optional RECALLIT_EXAMINER_KEY) —
e.g. a local Ollama: RECALLIT_EXAMINER_URL=http://localhost:11434/v1 RECALLIT_EXAMINER_MODEL=qwen2.5:1.5b. With neither configured it gracefully degrades to a
stricter keyword-ish floor over the same checkpoints (verified: no provider → no crash, just a
stricter grade — never a thrown error either way). Honest caveat, verified against a real 1.5B
local model: small models grade noticeably harsher than Claude on the same answer — the
anti-fabrication check drops their sloppier evidence citations, which fails safe (under-credits,
never over-credits).recallit daily / agent / talk / quickstartThese commands exist and are documented in recallit --help, but they're built for a live
terminal: they block on a synchronous prompt() read and drive recallit's own internal
Claude-Agent-SDK session (spends ANTHROPIC_API_KEY). Run one from a non-interactive agent
tool-call (no real stdin) and it ends immediately with 0 turns reviewed — verified by running
daily --help and agent --help against the published CLI: both ignored --help entirely,
launched a real session, and terminated only because stdin read as empty. Don't use them as
the drill loop in this skill. Use due + answer instead (below) — no key required, and the
agent turn is you, in the chat you're already having with the user.
(Aside: none of the --help flags are actually implemented anywhere in the CLI — every
subcommand just treats --help as an ignored flag and runs for real. Don't run any recallit
command "just to see the help text"; read this skill or recallit with no arguments instead,
which does print real usage.)
A pack is plain files under packs/<id>/:
packs/<id>/
manifest.json # { schemaVersion:1, engine:">=0.1.0", id, name, modality, meta }
cards.json # all cards; the gate stamps meta.status:"needs-review" on flagged ones
.author/source.txt # the grounding corpus of record (raw extracted source text)
cards.json is the single source of truth. recallit pack write (no LLM — deterministic) stamps
meta.status:"needs-review" on any card whose quote isn't a literal substring of the source, and
recallit topic add installs only the ready cards, skipping needs-review automatically.
.author/source.txtExtract the source's text using whatever file-read / web-fetch tools you have available, and save
it verbatim into packs/<id>/.author/source.txt — the corpus every card must quote from. Pick
<id> as a kebab-case slug of the title.
| Source | How |
|---|---|
| PDF / local file | Read it directly; for a long document, read in page/section ranges. |
| URL / article | Fetch it for clean text. If a JS app / paywall returns nothing usable, say so. |
| Concept (no doc) | Search the concept + subtopics, fetch a few reputable results, write the evidence (quote + url per line) into source.txt. Set meta.grounding: "web" on the manifest. The concept name seeds queries — it is never card content. |
| Code repo | Clone (shallow) to a temp dir; use README / package manifest / exported types as the surface. |
Precondition — do not skip: if the corpus is empty or near-empty (image-only PDF, paywalled page with nothing extracted), abort with a clear message. Never emit cards from an empty corpus.
manifest.json{
"schemaVersion": 1,
"engine": ">=0.1.0",
"id": "<slug>",
"name": "<Human Title>",
"modality": "text",
"meta": { "source": { "kind": "pdf|url|repo|concept", "ref": "<path-or-url>" }, "grounding": "source" }
}
Use "grounding": "web" for concept packs.
Walk the corpus front to back. For each fact/point worth remembering, decide which of two kinds it is — this decision is what determines how it gets graded later, so make it deliberately, not by default:
(a) Flashcard — exactness is the point (a term, a date, a definition, a quote, a formula, vocabulary). Emit:
{
"type": "qa",
"front": "What is the two-minute rule?",
"back": "Scale a new habit down so it takes under two minutes to start.",
"context": "optional surrounding sentence",
"tags": ["habits"],
"meta": { "sourceQuote": "<VERBATIM span copied from source.txt>", "locator": "p.162" }
}
This grades via lexical match against back (see "The grading contract" above) — the learner
needs to reproduce close to this wording. Right for anything where the wording is the answer;
wrong for "explain this in your own words" material, which the strict match will mark wrong even
when correct.
(b) Checkable item — comprehension is the point (explain why X, the argument's key points, a mechanism, "what does this mean") — a single quote can't capture the whole answer. Emit:
{
"type": "explain",
"front": "Why does the two-minute rule work?",
"back": "It removes the activation-energy barrier to starting, so the habit forms before willpower is needed.",
"meta": {
"grader": "coverage",
"rubric": [
{ "id": "barrier", "claim": "lowers the barrier to starting", "required": true, "sourceQuote": "<VERBATIM substring>" },
{ "id": "forms-first", "claim": "the habit forms before it requires willpower", "required": false, "sourceQuote": "<VERBATIM substring>" }
]
}
}
This grades via recallit answer's coverage/examiner path (a key present): the learner passes by
covering the required checkpoints in their own words — a correct paraphrase is Good, not Again.
Without a key, it degrades to a deterministic keyword-ish floor over the same checkpoints (still
never crashes, just stricter). back here is a concise exemplar for human review, not the
grading contract.
Honesty rules, strictly, for both kinds:
sourceQuote (the flashcard's one, or each rubric checkpoint's) must be copied verbatim
from source.txt — a literal substring. No quote, no card/checkpoint — the gate holds whatever
isn't grounded (quote-not-in-corpus for (a), rubric-point-not-in-corpus:<id> for (b)).back (and each rubric claim) must be entailed by its quote, not outside knowledge.back that aren't in the quote/context
(checkable items skip this check — back there is an exemplar, not the grounding contract).required: true.Bias toward fewer, sharper items overall: an article → ~15–30; a chapter → ~10–20; mix both kinds as the material actually warrants rather than defaulting to all-flashcard.
Draft ~3 sample cards from the first chunk, confirm each sourceQuote is a literal substring of
the source, show them, and offer to steer before spending effort on the whole source. Skip in
one-shot mode.
Write all drafted cards to packs/<id>/cards.json (a plain JSON array, no status field yet):
recallit pack write packs/<id>
# → "23/25 ready, 2 need review (grounding: source)" + each flagged front + reason
Reason codes: quote-not-in-corpus, missing-source-quote, rubric-empty,
rubric-point-not-in-corpus:<id>, unverified-number, unverified-proper-noun,
duplicate-front, quality:*. recallit pack review packs/<id> lists the flagged cards any
time, no LLM involved in either command.
Tell the user honestly: N ready, M need review, the pack path, and the flagged fronts + reasons.
Confirm first in conversational/ambient modes ("Install these N cards as topic ''?"). For
grounding:"web" packs, always show the unverified note and require an explicit yes, even in
one-shot mode.
recallit topic add packs/<id> # installs READY cards only — skips needs-review automatically
recallit stats --topic <id> # totalCards, dueNow
This is the part that's different from the Claude Code version of this skill: instead of an interactive terminal session, you run the loop yourself, one exchange at a time, in the chat.
recallit due --topic <id> [--limit n]
front to the user in chat and wait for their reply.recallit answer <cardId> "<user's exact reply>" --topic <id>
Again|Hard|Good|Easy) and reasons exactly as printed.If the user wants to self-rate instead of typing a free-text answer (Anki-style), use
recallit review <cardId> <Again|Hard|Good|Easy> --topic <id> — still just recording their own
call, not a judgment you make.
Useful alongside the loop:
recallit context --topic <id> # the engine's weak-spot notes for this topic, if any
recallit preview <cardId> # see a card's current schedule
recallit stats --topic <id> # totalCards, dueNow
recallit pack edit <id> "<instruction>" # e.g. "add 10 cards on chapter 5", "fix card 7"
Re-reads cards.json + .author/source.txt, applies the instruction, re-gates. Additive edits
(only new cards) merge and preserve FSRS review history; changed/removed cards force a rebuild
that resets the schedule and asks to confirm first (--dry-run to preview without installing).
Manual fallback: hand-edit cards.json, then recallit pack write packs/<id> to re-gate, then
recallit topic add packs/<id> --force (note: --force resets FSRS).
recallit topic add <source> also accepts a GitHub ref, a git URL, an npm spec, or a .tgz —
not just a local dir:
recallit topic add github:owner/repo[#ref]
recallit topic add git+<url>
recallit topic add npm:<spec>
recallit pack share <id> # print the install string + browse URL for a repo-local pack
The substring gate verifies the quote is present in the source, not that back faithfully
interprets it. Flag cards whose answer adds numbers/proper-nouns not in the quote. Say "every
card cites a verbatim line from the source you can check" — not "every card is verified true."