---
name: Review
slug: review-2
category: Automation
description: Review clears due memory reviews with free recall, making retention a two-minute habit. It loads the spaced-repetition queue, applies amnesty after absences, and walks the learner through graded recall sessions.
github: "https://github.com/nagisanzenin/engram/tree/main/skills/review"
language: Python
stars: 1348
forks: 102
install: "npx degit https://github.com/nagisanzenin/engram/tree/main/skills/review ~/.claude/skills/review"
installs_to: ~/.claude/skills/review
source_path: skills/review/SKILL.md
collection_size: 3
category_size: 1523
collection_url: "https://dirskills.com/collections/nagisanzenin/engram"
added: 2026-08-19T07:28:06.265Z
last_synced: 2026-08-19T07:28:06.265Z
canonical_url: "https://dirskills.com/skills/review-2"
---

# Review

Review clears due memory reviews with free recall, making retention a two-minute habit. It loads the spaced-repetition queue, applies amnesty after absences, and walks the learner through graded recall sessions.

**Install:**

```bash
npx degit https://github.com/nagisanzenin/engram/tree/main/skills/review ~/.claude/skills/review
```

## README

# /review — the retention loop

Read `skills/_shared/dialogue-grammar.md` (hard rules, confidence integrity, park-and-resume, and the rating map apply here verbatim). Set:

```bash
# Resolve the engine. RUN THIS BLOCK VERBATIM — do not substitute a path you guessed.
for d in "$OPENCODE_PLUGIN_ROOT" "$CLAUDE_PLUGIN_ROOT" "$CODEX_PLUGIN_ROOT" "$ENGRAM_ROOT" \
         "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/extensions/engram" \
         "$HOME/.gemini/config/plugins/engram" \
         "$HOME/.pi/agent/git/github.com/nagisanzenin/engram" \
         "$PWD" "$(git rev-parse --show-toplevel 2>/dev/null)" \
         "$HOME/.agents/engram"; do
  [ -n "$d" ] && [ -f "$d/scripts/engram.py" ] && ENGRAM="$d/scripts/engram.py" && break
done
if [ -z "$ENGRAM" ]; then
  echo "engram: engine not found — set ENGRAM_ROOT to your engram checkout" >&2
  return 2 2>/dev/null || exit 2   # FAIL CLOSED: proceeding runs `python3 ""`,
fi                                  # which dumps a python usage error at the learner
```

If none are set, resolve the plugin root as the directory containing `.claude-plugin/plugin.json` (or `.codex-plugin/plugin.json`). **Never inline a learner's answer into a shell command** — pass productions via `--production-file` (or `--production-file -` on stdin); a stray quote or `$(…)` in what they typed would otherwise execute.

**Spawning agents.** "Spawn **engram-…**" means a *fresh-context* child running that agent's definition — via your platform's subagent/Task tool (the type may be namespaced, e.g. `engram:engram-assessor`). **If your only mechanism is a generic `sessions_spawn` — or you have no spawn tool at all (Pi) — read `skills/_shared/subagents.md` first.**

## 1 · Load the queue

```bash
python3 "$ENGRAM" session-start   # what the ambient hook ALREADY told them (read-only)
python3 "$ENGRAM" stash count     # a previous session's ungraded work?
python3 "$ENGRAM" due --cap <cap>
```

If stash > 0, settle it first (assessor → `receipt` → `stash clear`, per /learn step 4) with one explanatory line. Caps: `quick` → 5 items; otherwise mode default (Standard ≈ 12). `--topic <t>` if the user named one, but note interleaving across topics is the default *on purpose* — don't undo it for tidiness. Open with the session ticket. Empty queue → one line of honest celebration, then stop (suggest `/learn continue` only if a topic has frontier nodes). Never invent reviews.

**`--cap` picks WHICH items, and it is not the order Engram used to serve (v1.3).** A capped session is a triage decision: with `--cap` the engine ranks by *expected 30-day retention saved per expected minute* and returns `{order, order_basis, items}` (the older `--limit` still returns a bare list in the old most-overdue-first order). Two things to carry into the session, both already in the payload:

- **Say nothing about the ordering unless asked** — it is plumbing, not a lesson. If asked, the honest line is in `order_basis`: it is *model-derived* (an FSRS projection), backed by one strong human RCT for the policy family and by simulations, and **no human RCT has ever ranked backlog orders**. Never present it as proven.
- **Never quote `expected_minutes` as a session estimate.** It is a ranking weight (a cold item is priced as slower), and it disagrees with the ~0.6 min/item the hook and `decay` use *on purpose*. When you need to say how long something takes, use `decay`'s `minutes` or the hook's — one figure to the learner, always. (Those two are the same estimate; `expected_minutes` is a per-item weight whose best-case rung happens to be 0.6.)
- **Items flagged `effectively_relearn: true` are functionally re-learns, not reviews.** They sort last on purpose and they lose almost nothing more by waiting. Name them once at the close rather than burning the cap on them: *"three of these are past the point of a quick review — they'd want a re-derivation. Want them in a longer session, or shall I retire any?"*

**Return-after-absence (the amnesty protocol — the highest-evidence Layer 2 move; `docs/05-affective-layers.md` P14).** Fires when `due > 2× the mode cap`, **or** when `adherence.return.days_since_last_session ≥ 7` (the engine's own constant — the same one the hook's amnesty and decay lines use; don't invent a different threshold), **or** when the loop has never closed. Then do **not** dump the debt.

> **The hook already spoke, and it spoke first.** On the same trigger, `session-start` has printed — in this order — amnesty, their plan, and the decay cost, *before the learner typed anything*. **Run `python3 "$ENGRAM" session-start` yourself in step 1** (it is read-only, stateless, and safe to re-run) so you can see what they already read, then **do not repeat any of those three lines.** Your amnesty is a re-frame only if they haven't just read one; repeated, it is padding, and repeating the plan line is exactly the "never twice, never as leverage" the protocol forbids. This is the #1 SRS churn trigger, and a wall of overdue reviews reliably makes people quit (Silverman & Barasch 2023; a single missed day does not actually harm memory — Lally 2010). Instead, one calm line of amnesty + load renegotiation, then a real choice:
- Frame it as normal, owed nothing: *"You've got 40 due after the break — that's just spacing doing its job, not a debt. FSRS handles backlog fine."*
- **Show their own plan back, once, if they have one** (`model` → `settings.commitment`): *"your plan was: when I open the terminal in the morning — I clear one review."* Their sentence, verbatim, no commentary, no "so let's stick to it." It is a reminder of what *they* decided, and it is the highest-evidence thing in this protocol (re-prompting a stated plan is exactly what the RCTs tested). Then move on — never twice, never as leverage.
- Offer (arrow-key): **clear a capped set today** (this mode's cap — recommended) / **a longer catch-up** (`--cap` at ~2× the mode cap; an over-cap clamps to the queue, so it is always safe) / **just one topic** (`--topic <t>`; **drop this option entirely when there is only one topic** — it is identical to the others and reads like a glitch). Never a marathon; the two-minute floor is a floor, not a target.
- Then run only the chosen cap (`due --cap <n>`). What's left stays due and un-guilted. Zero shame in either the offer or the close.
- **`retire` belongs in this conversation, and only here** (v1.3): when the queue is genuinely stale — a topic they've moved on from, a node that no longer matters — offer it plainly: *"anything here you'd rather take off the list? `retire` keeps it in your history, out of your queue."* Then `python3 "$ENGRAM" retire --topic <t> [--node <n>]` (reversible: `--restore`). **You never name which nodes to retire.** Auto-suggesting the ones they keep failing is a flattering denominator wearing a helpful face; the learner decides, the engine records and counts (retired items stay visible in `adherence` and `retention.unmeasured`).

**The honest number — but only if the hook has not already said it (v0.6).** Amnesty removes the guilt; it must not also remove the *stakes*. After the amnesty line and **before** the arrow-key offer, read the engine and state what the decay actually costs — one line, then move on:

```bash
python3 "$ENGRAM" decay --topic <t>     # or bare, for everything
```

Its `read` field is already written for a human. Say it flatly, in the register of a lab notebook reporting a result — **and in your own words, not the hook's**, since `session-start` prints that exact sentence on the same trigger: *"Seven of these have drifted to about 70%; a few minutes now is the difference between keeping them and re-learning them."* If the hook already said it, **say nothing** and go straight to the offer.

The rules that keep this from becoming the thing this project despises:
- **Information, never pressure** (`docs/05` P13; Deci/Koestner/Ryan 1999: controlling praise nets **d = −0.78** on adult motivation). It reports a forgetting curve because that is what the curve says. **No "should." No scold. No "don't lose your progress!"**
- **Once, on return.** Not every session, not per item. The engine's ambient hook already rations it (it fires only on a never-closed loop or a real absence); do not re-say what the hook already said.
- **Amnesty first, always.** The order is: *nothing is owed* → *here is what it costs* → *here is a two-minute path*. Reversed, it is a debt collector.
- **`settings.decay_notice = "off"` means silent.** The learner opted out; honor it without comment.
- If a line would read to a skeptic as *"the tutor is trying to make me feel guilty,"* it is a defect. Cut it.

## 2 · Per item — the retrieval protocol

The `due` payload gives you `probe`, `claim` (canonical answer), and `rubric` — plus `node_kind` and `practice` (v1.1). Show a progress marker per item: `[3/6] · residual-stream †`. The order of operations is sacred:

**Concept discrimination (v1.14 — the P17 concept clause, docs/16 §5; an instrumented hypothesis, not a settled magnitude):** when a concept/fact item's `contrasts_with` payload names a sibling (the engine only sends siblings the learner has already encoded — never `new`, retired, or the node itself), you may open the item with ONE discrimination step, served **adjacently in the same turn**: construct **two fresh minimal instances** — concrete situations, one falling under each concept, surfaces held parallel — and ask: *"which is which, and what single feature decides it?"* **Build instances; never quote claims.** The payload's claim texts are construction fuel only — the same rule that makes a procedure drill serve fresh values, never the stored numbers. If either claim's wording (or anything the item's rubric marks) would have to appear in the pair to make it work, or the item's own probe already asks for the contrast, **skip the drill**: rule 1 below ("the probe only, free recall, no hints") outranks this step, and a pair that primes the probe manufactures a `recalled` the schedule then believes. Open production, never a menu; until it's answered the progress marker shows the topic, not the node id. The discrimination answer is context for the gap analysis, but the **grade is the probe production's alone**, and the sibling gets **no receipt and no schedule change** (a house decision — docs/16 §7.8 tracks the drift risk). Exempt: `arbitrary: true` items (blocking beats interleaving for vocabulary, g = −0.39), `quick` mode, and any pair that isn't genuinely confusable — when in doubt, skip; the drill seasons a review, it never taxes one. Never split the pair across the queue: adjacency is the mechanism.

**`node_kind: "procedure"` items first take a detour** (Read `skills/_shared/problem-grammar.md` once per session when one appears): serve a **fresh algorithmic variant** generated from `practice.problem_frame` — new values, same structure and cover story; never the stored numbers, and never a re-clothed isomorph (that is `transfer_probe`'s job) — computing the answer key by execution before showing anything. When a `practice.discriminates_from` sibling is co-due (or mature), serve the pair **adjacently** and open with the naming step ("which technique, and why?") — and until it's answered, the progress marker shows the topic, never the node id (the id would answer the question). Grade the solve with the problem grammar's table — method-wrong caps the grade regardless of the answer; a slip-only miss is `partial`/`hard` with `--error-class slip`, a wrong-method one carries `--error-class conceptual`. A procedure node with no usable `practice` falls back to the stored probe, concept-style. Everything below (confidence pick, stash-or-rate flow, transfer, momentum) applies unchanged.

1. Show the **probe only**. Free recall — no options, no hints in the prompt, no "remember when we...". Do **not** ask them to type a confidence number.

   **If an experiment arm applies to this node (v1.9)**, honor it here: `experiment status` names the active design, and `probe-variation`'s `varied-probe` arm means you **rephrase** the probe — different opening, different framing, **same rubric criteria, same thing asked**. Never add or drop a required element; difficulty drift is the named threat to that design's validity. The `stored-probe` arm gets `node.probe` verbatim, always, for the life of the experiment. Arm-assigned productions are **stashed for the blind assessor**, not self-graded, so both arms are scored by one oracle.
2. They produce. (Silence is fine; "no idea" is an answer — treat as lapse, warmly.) **Then collect confidence by calling `AskUserQuestion` (the four-band Confidence picker — exact call in grammar ⚠), BEFORE the reveal.** Skip only if they volunteered a number unprompted; "Other"→exact number; dismiss/skip → null, never estimated.
3. Reveal: canonical `claim` + a one-line gap analysis against `rubric` — specific, about the work. If they gave consequence-only, run the terse-production move (one "and the mechanism?" — grammar file) *before* the reveal. (Confidence picker, if any, comes first — sureness before feedback.)

   **⚠ Before you mark a criterion missed, check that the probe asked for it (v1.10, issue #13).** Read the probe you just served, alone, and ask what the best answer to *that exact question* would **necessarily** contain — the same one bar the assessor uses. If a criterion would not reliably appear in it — it wants a consequence nobody was asked to draw, or its content is already sitting in the probe's own stem so it could only be earned by repeating the question back — then the card is broken, not the learner. Judge that from the probe alone, never from what they happened to say: a criterion they volunteered unasked is still unfair. Say so in one line, grade the production exactly as it stands (an unfair criterion never lifts a grade — that is how a wrong number gets believed), and repair the card before moving on:

   ```bash
   python3 "$ENGRAM" edit-node --topic <t> --node <n> --file <tmpfile.json>
   # tmpfile.json = {"probe": "<the same question, now also asking for what that criterion marks>"}
   ```

   Write the file first — a rewritten probe is free text you just authored, and the shell-safety rule covers it exactly as it covers a learner's production.

   Schedules, receipts and registrations are untouched by this; only the contract moves. On assessor-graded items the same signal arrives as `probe_gap` in the returned item — treat it identically. `doctor` lists every node in this state under `probe_gaps` if the learner would rather sweep them all at once.
4. Map to a rating with the shared table (round down when torn) and commit **immediately**. Pass the learner's answer via a file (write it, then reference it) so their text never lands on the command line:

```bash
python3 "$ENGRAM" rate --topic <t> --node <n> --rating <r> --confidence <c-or-omit> \
  --grade <g> --production-file <tmp-answer.txt> --kind review --source self
# procedure items only: append --error-class slip|conceptual per the problem-grammar
# table (omit the flag entirely on concept/fact items and on recalled grades).
# If the engine rejects --error-class (an older engine than these skills), RETRY the
# same command without the flag — the rating must never be lost to a version skew.
```

Relay the returned due date in passing, not ceremonially ("back in 12 days"). **When the growth line and the next due date disagree** — durability says weeks, the next review lands in days (`schedule_policy` in the payload: the relearning dose caps early intervals) — say the one honest sentence: *"the next check comes sooner than that on purpose — early reviews are front-loaded while a memory is young."* Never let the learner read the early date as the growth line being false (v1.14 §5.6 finding). **When the `rate` output's durability crosses a threshold** (first reps, or `s_after` clearing ~7 or ~30 days, or roughly a doubling — a milestone, not every review; grammar file, Pillar 13), add *one* flat growth line — *"that jumped from ~4 days to ~17; it'll hold now."* A mature node creeping up says nothing new — stay silent; a `hard`/`again` gets honest task-feedback, never a manufactured win; silent too if `settings.momentum` = `off`.

**If the item comes back `lapsed` — or `partial` with the node's central claim absent (concept/fact): do not move on.** Run the criterion loop in the dialogue grammar — grade it, re-derive, put another item in between, then re-ask, up to 3 passes, stopping at one clean recall. Rate re-attempts with `--relearn --attempt <n>`; they record the loop and change no schedule. The mode budget still outranks the criterion.

**Special cases:**

- ### ⭐ `transfer_ready: true` — SERVE THE HARDER QUESTION (v0.8)

  The `due` payload now carries `transfer_ready` and `transfer_probe`. When it is `true`, the node is **mature** (stability over 21 days across 3+ retrievals) and the architect wrote a probe that asks the same idea **wearing different clothes** — usually from the learner's own world.

  **Serve the `transfer_probe` INSTEAD of the `probe`**, and rate it with `--kind transfer`:

  ```bash
  python3 "$ENGRAM" rate --topic <t> --node <n> --rating <r> --confidence <c-or-omit> \
    --grade <g> --production-file <tmp-answer.txt> --kind transfer --source self
  ```

  Say what you're doing, plainly and once: *"You've held this one for a month, so let's not ask you to recite it. Let's see if it fires."*

  **Why this exists, and why it is not decoration.** `transfer_probe` has been authored by the curriculum architect since v0.1 and **read by nothing** — 12 of the 13 nodes in the founder's own graph carry one, and zero transfer receipts existed anywhere, ever. Engram measured *memory* and claimed *capability*. There is a sharper version of that critique which `docs/07` §8 takes seriously rather than deflecting: **transfer-appropriate processing** says practice should match use. If the learner's goal is *to do* — write the code, make the call — and every review is verbal free recall, Engram may be training a different skill from the one that was paid for. This is the answer to that.

  **Grade it honestly, and separately.** A transfer receipt is **never pooled into retention** — `stats.transfer` is its own number with its own denominator, because "did the memory survive?" and "does the capability fire?" are different questions. A lapse here is **not** a memory failure and must never be framed as one: *"you remember it fine — it just doesn't fire yet. That's a different muscle, and it's the one that matters."* Do not manufacture a failure out of a hard question.

  **And the engine now backs that sentence up (v0.8.1).** A failed transfer probe **leaves the memory schedule completely untouched** — same stability, same due date, no lapse recorded. Until v0.8.1 it did not: one failed probe deleted **97% of a mature memory's durability** (s 443 → 12), flipped the node to `learning`, and dropped it below the transfer bar forever. **Answering a harder question wrong demolished the schedule for the original concept** — the exact "fabricated setback" the maturity gate was built to prevent. A successful probe still strengthens the memory, because applying an idea *is* a retrieval, and a strong one.

- **High confidence (≥70) + lapse** — hypercorrection gold: pause the queue, have them re-derive the claim from its `why_chain` prerequisites (or rebuild the mnemonic if `arbitrary`), log `misconception add`. Two minutes here is worth ten elsewhere.
- **First lapse on a topic, once ever** — if they say anything about the habit not sticking, one flat line and nothing more: *"habits like this typically take about two months to feel automatic, the range is huge, and single misses genuinely don't matter."* (Lally 2010; PNAS 2023.) **Never a day count, never a countdown, never "you're on day 12"** — the constant does not exist, and a countdown is a streak with better manners.
- **Second+
