Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The alix book

alix is a plain-text spaced-repetition tool built around understanding, not just recall. A fast flashcard core does the drilling, and AI fills in where it helps: a tutor on any card, decks and workspaces generated from your own material, and an exam that grades your understanding against the source.

This book is the manual and reference: each feature is expanded with worked examples and the reasoning behind it, and the Directives reference is the quick index to every directive.

Start with Why alix exists for the motivation, or jump straight to Getting started.

1 · Why alix exists

Most study tools are built to help you remember. alix is built to help you understand, and the gap between those two is the reason it exists.

Spaced repetition is one of the few genuinely proven ideas in learning: review a fact just as you’re about to forget it, and it sticks. Tools like Anki turned that into a daily habit for millions. But they share a blind spot. They optimize the retention of isolated facts, and they quietly accept a failure mode every serious user eventually feels: you can answer every card correctly and still not understand the thing. Recognizing an answer is not the same skill as being able to derive it, explain it, or see why it follows. You can have a deck at 100% and a head full of trivia you can’t actually use.

alix starts from that gap. It keeps the proven core (drilling facts on a spaced schedule) but treats it as only the first step: the part that loads the raw material. On top of it sit two things ordinary flashcards can’t do.

Traces teach you to follow a mechanism, not just recall a fact. A trace is a walk along a real chain of reasoning through a real source (a data flow through code, the steps of a proof, the clauses of a contract) where at each step you predict what comes next before it’s revealed. It trains the thing experts actually have: not a bag of facts, but the chain of because this, therefore that.

The exam checks that you understood, not that you memorized. Once you’ve drilled a topic, alix examines you with fresh questions generated from the source material itself, never from your cards, because grading you on your own cards is circular and trivially passable. An AI examiner reads your answers against the source and decides whether you’ve actually got it. Only then does the topic count as mastered, and only then does it unlock what depends on it.

That last word is load-bearing. alix only calls something mastered when your understanding has been tested against the ground truth and held up. A green checkmark you didn’t earn is worse than none (it’s false confidence) so the examiner is built to be a real examiner, not a flatterer.

Who this is for

alix asks more of you than a flashcard app, and gives more back. It removes the tedious part (an AI can generate decks, build traces, and lay out a whole curriculum from sources you point it at) but the work it asks of you is harder: predicting, explaining, deriving, being examined. It’s a power tool for people who want to understand something difficult on purpose and want proof that they do: a new codebase, a field, a hard paper. If you mainly want to cram names and dates, a simpler tool will serve you better.

The bet

There’s a wager underneath all of this. As AI gets better at holding and retrieving facts, the scarce thing for a human mind shifts: away from retention, toward understanding and judgment. The facts you can always look up. What you can’t outsource is the structure in your own head: reasoning through a problem, knowing when an answer is wrong, moving fast because you genuinely grasp the terrain. alix is a tool for deliberately building that structure, and for using AI not to do your thinking, but to teach and to test it.

The rest of this book is how.

2 · Getting started

Install

alix is a single Rust binary. The shipped paths, no Rust toolchain required for the first:

curl -sSf https://alix.study/install.sh | sh   # prebuilt release binary
cargo install alix                             # from crates.io (needs rustup.rs)

The installer fetches the release asset for your platform; every asset also has a .sha256 beside it on the GitHub releases page to verify a manual download. Building from a checkout works too (git clone, then make install).

Any of those puts alix on your PATH. Check it:

alix --help

The flashcard core (reviewing, scheduling, every answer mode, browse, and the web app) runs with nothing else installed: no accounts, no network. The AI features (deck generation, the exam, traces, workspace generation, and the in-session tutor) shell out to a supported model CLI, Claude Code by default; the Gemini, Codex, and Copilot CLIs are also supported. Install at least one and authenticate with it. See chapter 16 for how to switch backends. You can use the entire core without ever touching the AI layer.

Your first deck

On a first run (no decks directory yet), alix creates it and seeds The alix tutorial, a small deck that teaches alix while you review it, from grading honestly to the deck format below. Its last card tells you to delete it; alix seeds it only into a brand-new decks directory, so once deleted it never returns. If you already have decks, nothing is seeded.

A deck is a plain .md file. A card is a ## line (the question) with its answer on the plain lines beneath it:

## What does SRS stand for?
Spaced repetition system.
> It schedules each card just before you'd forget it.

## Which scheduler does alix use?
FSRS, which predicts when you're about to forget each card.

Save it as srs.md in your decks directory (~/decks by default). A line starting with > is a note, shown after you answer. Initialize a file you wrote by hand once:

alix deck init ~/decks/srs.md

Initialization assigns the stable deck and card IDs that preserve review history. It also tells alix that this Markdown file is a deck; other .md documents in the same folder remain ordinary files.

Review it

alix

alix opens the web app (printing its URL); pick srs.md there and Learn it. The question shows in the browser; you recall the answer, press a key to reveal it, then grade yourself: failed (you missed it), partly (got the gist but stumbled), or passed. Your grade moves the card along its schedule, so cards you know come back rarely and cards you miss come back soon. That self-graded reveal is flip mode, the default; later chapters cover the modes that make you type the answer, pick from choices, or reveal it line by line.

When nothing is due, there’s nothing to review; come back when cards mature.

The deck picker

That page alix opens is the picker, over your decks directory (~/decks by default; change it with decks_dir in the config). It groups your decks into Workspaces, Recent, and Folders and is driven by Vim-style keys (j/k to move, Enter to open, / to filter by name). Every review starts here; there’s no direct deck launch. This is what the desktop launcher opens. Focus a deck and press Browse to read through its cards with no grading or scheduling.

The everyday commands

alix stats srs.md     # a progress overview
alix list srs.md      # every card with its per-depth schedule and due time
alix doctor srs.md    # lint the deck (syntax errors, duplicate cards)
alix reset srs.md     # clear stored progress (also --card / --all)

A session is one deck; review them one at a time. From here the book goes deep: the next chapter is the deck format in full, then reveal & session depths and scheduling.

3 · The deck format

A deck is a plain-text Markdown file. You can write one in any editor with no tooling, read it back at a glance, and because it’s real Markdown, it renders sensibly anywhere else too: a preview pane, your file host, GitHub.

When you write a deck by hand, initialize it once before it appears in the picker:

alix deck init ~/decks/my-deck.md

The command adds stable deck and card IDs without rewriting the authored content. A valid id: deck-<token> in the opening frontmatter marks the file as an initialized deck. The deck- prefix on the value is what carries the meaning: it is how alix tells its own decks apart, and the same prefixed string travels everywhere the id appears, in a frontmatter key, a card marker, a filename, a prerequisite reference, or an error message. Markdown without that prefixed id is never listed or modified, so ordinary documents with ## headings can sit in a decks folder untouched. Generated, imported, received, and tutorial decks are initialized when they are created.

Choosing a card shape

Read the material before choosing its card shape. The shared guide below names the useful choices and distinguishes structural matches from judgement calls. The sections after it show the exact syntax for each shape.

Which card shape suits which material. Read the material first, then pick the shape; do not pick a shape and bend the material into it.

Some rows are structural: the material has a property the shape exploits, and any other shape wastes it. Some are judgement: more than one shape is defensible and the choice is yours. The difference is marked, because a rule that claims uniform authority gets followed badly exactly where thought was needed.

materialshapekindwhy
Paired items: a word and its meaning, a term and its definition, a symbol and its name.A card table: a GitHub pipe table, one row per pair, columns front, back, and an optional note.structuralOne row per pair, and each row’s Recognize options come from its own column, so the wrong answers are real siblings and cost no AI call. Prose wastes both.
Ordered steps that must be reproduced in order: a recipe, an algorithm, a procedure, a verse.reveal: line, with one step per answer line.structuralOrder is graded, and the answer uncovers one line at a time so recall is stepwise rather than all-or-nothing. A flip card cannot test order at all.
An answer that cannot be typed: a diagram, a circuit, a glyph, notation.input: drawstructuralThe learner sketches and self-grades against the reveal. Typing a diagram is not a check, it is a workaround.
A statement turning on one term, where the sentence around it is the cue.Cloze: wrap the hidden span as \blank{...} in an answer line.judgementThe context does the cueing, so recall is anchored where it will be used. If nothing in the sentence is a natural target, this is a plain card wearing a disguise.
A fact whose common confusions are known and nameable.Authored multiple choice: a task list with one - [x] and two or more - [ ].judgementThe distractors are the teaching. Write them only if you can say what mistaken belief each one represents; if you cannot, the shape is doing nothing.
A term that must be recalled from either side: vocabulary, symbols, names.direction: bothjudgementOne authoring act, two cards. Reach for it when both directions are genuinely useful, not by default: it doubles the review load.
Anything else: a definition, an explanation, a cause, a comparison.A plain card: ## front, answer lines below.judgementThe default, and not a failure. Most material has no structure to exploit, and a plain card drilled well beats a clever shape drilled badly.

Rules that hold whatever the shape:

  • Every card needs at least one answer line.
  • Most cards deserve a > note: an example, a caveat, a mnemonic, or why it matters. Never a restatement of the answer.
  • One idea per card. Split compound facts rather than nesting them.
  • No two cards may test the same fact. Vary what is asked; do not rephrase.

order: and sampling: are not shapes. They modify how an existing deck is served and are documented with the other directives.

Cards

A card starts with ## at column 0, the front (the question). The lines beneath it are the answer (the back), written plainly, and may span several lines:

## What is the capital of France?
Paris.

## Name the three additive primary colors.
Red
Green
Blue
<!-- reveal: line -->

A physical newline inside an ordinary flip answer is a Markdown soft wrap: the adult and mobile clients display it as a space, so you can wrap long source lines for editing without creating visual gaps on the card. Add <!-- reveal: line --> when the lines themselves are the learning sequence; line reveal and line typing preserve them individually.

Inline formatting

Card fronts, answer lines, and note prose support **bold**, *italic* or _italic_, and inline `code`. Inline code is verbatim, so `**literal**` displays the asterisks instead of bold text.

Formatting has two projections: styled display and plain content. Grading uses the plain content, so type Paris, not **Paris**. To keep emphasis markers literal, escape them with backslashes such as 2\*3\*4, or wrap the text in inline code such as `2*3*4`. Run alix doctor <deck> to find card text that will render as emphasis.

LaTeX math

Use $...$ for a formula inside prose:

## Why does $a^2 + b^2 = c^2$ describe a right triangle?
It is the Pythagorean theorem.

Use $$...$$ for display math. The two delimiters and the formula must occupy one whole logical line; a multi-line $$ block is not supported:

## What is the Gaussian integral?
$$\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$$

An opening dollar must touch the first formula character, and a closing dollar must touch the last one. A closing inline $ cannot be followed by a digit, so $5 and $10 stays literal currency. Escape a literal dollar as \$. Unmatched dollars and $$...$$ surrounded by prose also stay literal. Dollars inside inline code or fenced code are always verbatim.

Graphical clients render recognized math with the shared RaTeX renderer. If the delimiters are valid but the LaTeX is malformed, the card still loads and shows the source with a visible “math could not render” message. alix doctor <deck> reports the card line, formula snippet, and renderer error.

Grading uses the content between the delimiters, so type x^2, not $x^2$. Adding or removing math delimiters does not change a card token or stale its cached augmentations. Generated output that otherwise parses as a deck is checked before placement; malformed math cannot replace an existing deck.

A ## only starts a card at column 0 and outside a code fence. A ## that is indented, or sits inside a fenced block, is ordinary answer content, so a Markdown heading in a sample, a shell comment, or a Dockerfile line needs no escaping:

## What does this script print?
```bash
echo hi
## this line is just part of the answer, inside the fence
```

Multi-line fronts

When the question itself spans more than one line, a --- divider marks where it ends and the answer begins:

## What does `lo` control in this signature?
def bisect_right(a, x, lo=0, hi=None)
---
The lowest index the search considers; entries below `lo` are ignored.

Here the front is two lines (the prose question plus the code it’s asking about), and without the --- alix couldn’t tell where the question stops and the answer starts. (A one-line question needs no divider: the answer just follows on the next line, as in the cards above.)

Multiple-choice (checkbox) cards

Write the answer as a GitHub task list to supply your own Recognize options:

## Which number is prime?
- [ ] 4
- [x] 5
- [ ] 6

The single [x] item is the correct answer. Alix shows only that answer at Recall and expects it at Reconstruct; the [ ] items are distractors shown with it at Recognize. Every option is used, so the card needs no AI choices augmentation and is skipped by that augment target. The Rust core shuffles the options: their order stays fixed while one question is on screen, then receives a fresh seed when the card reappears or a new study session starts. As with any shuffle, two appearances can still produce the same order by chance.

A checkbox card needs exactly one checked item and at least one unchecked item. Use -, *, or + bullets, with [x] or [X] for the answer. Put a literal task list inside a fenced code block to keep it a plain card answer. Task lists inside notes or a card’s front before the --- divider render as static checkboxes rather than interactive choices.

Card tables

Flat material at scale (a vocabulary list, countries and capitals, dates) can be one Markdown pipe table instead of a ## block per fact. Each row is a card: first column front, second column back, optional third column note. The header row is shown as the card’s context, never tested:

| word      | meaning   | note                 |
|-----------|-----------|----------------------|
| purported | angeblich | often in legal prose |
| feasible  | machbar   |                      |

The table must start at column 0 with a header row and a delimiter row (alignment colons are fine), exactly like GitHub renders it; every line starts and ends with |. Inside cells, inline formatting and math work as in any card text. A table inside a fenced code block stays literal text.

Give a table a title by putting a ## heading directly above it, with nothing between them but blank lines:

## Verbs of arguing
| English   | German      | usage                |
|-----------|-------------|----------------------|
| to refute | widerlegen  | eine These widerlegen |

The heading names the group and is shown as the card’s first context line, above the column labels; it is a title only when its body is empty, so a heading with an answer under it is an ordinary card that happens to be followed by a table. Directives written on the title line (including the table’s own ID) belong to the table.

At Recognize, a table card’s wrong options are drawn from its own column: the other rows’ answers are the distractors, so a table needs no AI choices augmentation and no authored options (though both take precedence if present). A row only gets a pick when its column offers at least three other distinct values; smaller tables stay reviewable at the other depths.

That column sampling is on by default. Turn it off for a table whose rows are not interchangeable (a mixed list, a table of one-off facts) with <!-- sampling: off --> among its directive comments, or set sampling: off in the frontmatter to make that the deck’s default and re-enable single tables with <!-- sampling: on -->. A table with sampling off and no other option source is simply not offered at Recognize, and alix doctor reports a sampling: key that can affect nothing.

Identity works like card IDs, per row. alix deck init (or opening the deck for review) mints one container ID line after the table, and a short stamp at the end of each row, after the closing pipe:

| purported | angeblich | often in legal prose | <!-- r:4k2x9w -->

Renderers drop cells beyond the header count, so the stamps stay invisible in a rendered view while keeping the source columns aligned. Both kinds of marker are machine-maintained, never hand-authored, and they travel with their row, so sorting, inserting, and editing rows preserves review history.

Directive comments between the table and its ID line (direction, reveal, input, sampling) apply to every row. direction: both doubles each row into a reversed card, which samples its options from the front column.

The format is deliberately narrow: two or three columns only, no cloze blanks or images inside cells, and nothing but directive comments between a table and the next card. Anything outside that shape is a parse error rather than a guess. And a table earns its place at dozens of rows; under roughly ten cards, plain ## cards read better and can carry everything a card can.

Notes

A line beginning with > is a note: shown after you answer, never part of what’s tested. Consecutive > lines join into one note:

## Why does TCP open with a three-way handshake?
To agree on initial sequence numbers in both directions.
> SYN, SYN-ACK, ACK: each side learns the other's starting sequence.

Keep the answer to the thing you want to recall, and put the why, the example, or the mnemonic in a note.

Title, and deck-wide settings

A deck’s title is a single-# heading. Deck-wide settings and its machine-maintained deck ID live in frontmatter: a ----fenced YAML block at the very top of the file, above the title.

---
format-version: 1
id: "deck-9w2c7x4k1m8q3z5t0v6b2n4d8f"
authors: [Alex, "Claude (Opus 5)"]
license: CC-BY-4.0
tags: [french, vocabulary]
created-at: 2026-07-31
reveal: line
order: sequential
---

# French vocabulary, chapter 4

format-version is the version of the deck format, not of the deck itself. alix deck init writes it above id, it stays 1, and alix refuses a deck declaring any other number rather than guessing at a format it does not know. It is written first because it says how to read everything below it, but alix accepts it anywhere in the block.

authors and tags take one value or a list; license and created-at are single strings, by convention an SPDX identifier and an ISO 8601 date. Put both people and any AI that helped in authors. These four are yours to fill in and alix never changes them.

Apart from id and format-version, frontmatter carries only what differs from the defaults, and a command-line flag always overrides it. Anything else you write before the first card is just prose (context, a reading order, whatever you like), so a deck can also read as a normal document. The full set of frontmatter and per-card keys gets its own Directives reference chapter.

Escaping

Because ##, >, ---, and the fence and cloze markers are structural, an answer line that must start with one literally is escaped with a leading backslash: \##, \>, \---. The backslash is consumed; the line displays without it.

## How do you write a second-level heading in Markdown?
\## Section title

Why editing a deck is safe

Every initialized deck and card carries a stable identity. alix deck init writes the deck ID as id: deck-<token> in frontmatter and each card ID as a <!-- id: card-<token> --> line. If you later add a card to that initialized deck, opening review or augmentation assigns the missing card ID. Those tokens, not the text, are what your review history hangs on. You don’t type or manage them; alix adds and maintains them after you explicitly initialize the file.

Because identity is the token and not the words, you can edit anything (reword the question, fix a typo in the answer, rewrite a note, reorder cards) and its history follows. The only thing that starts a card’s history over is deliberately replacing it. (alix doctor warns if an id line goes missing, for instance if an external tool stripped the HTML comments.)

So a deck is safe to refactor freely: your progress rides on the token, not on the words.

4 · Reveal & session depths

How a card is checked isn’t one setting you pick per card. It falls out of two independent things:

  • Reveal-method (how the answer is uncovered): authored per card (or deck-wide) with reveal:, because only the author knows the answer’s shape.
  • Session depth (how deeply you’re asked to retrieve it): chosen per session (Recognize, Recall, or Reconstruct), because only you know how well you want to know this material right now. It isn’t a deck directive, and not personal config either: it’s a property of the session you start.

alix derives the concrete check from the pair, so you never hand-write “type this one” or “explain that one.” Keeping them separate keeps presentation (the author’s job) apart from how deep you’re drilling (your call, per session).

The reveal-method axis: reveal:

Three ways to uncover an answer. Set it deck-wide with a reveal: line in the frontmatter, or per card with a <!-- reveal: ... --> directive (default flip):

  • flip (default): the whole answer is revealed at once.
  • line: the answer is revealed one line at a time, for ordered material (lyrics, a sequence of steps). Pair it with order: sequential in the frontmatter to walk the deck top to bottom.

A card becomes cloze (a gap to fill) automatically when its answer contains \blank{...} markers; the marker itself is the trigger, never a reveal: value. See cloze cards.

## Stage every change in git, including deletions?
git add -A

## Recite the opening.
Now is the winter of our discontent
Made glorious summer by this sun of York
<!-- reveal: line -->

A per-card <!-- reveal: --> overrides the deck’s; the deck’s overrides the default. It’s a review property, not content, so it’s not part of a card’s identity: adding or changing it never resets progress.

Session depths: Recognize, Recall, Reconstruct

Every review session runs at one of three independent depths, picked when you start it with the web picker’s split Depth… button, whose small ▾ opens a menu of the three (on the keyboard: v, then 1/2/3; Esc cancels; rebindable in [keys.picker]). The menu also carries the cram tick-box (c); see Cramming. Plain Learn reuses the deck’s own last-used depth, remembered per deck. The first time you ever open a deck, that default is Recognize if a genuine multiple-choice pick is ready to go: authored - [x]/- [ ] options on a card, AI-generated distractors (alix deck augment --target choices, or the web Augment screen), or a card table’s own column; otherwise it’s Recall.

  • Recognize: unscheduled, boolean, and pick-only. There’s no FSRS state for it at all, just a per-card recognized flag. It’s a genuine multiple-choice pick, built from a card’s authored task-list options (- [x]/- [ ]), the deck’s cached AI distractors (alix deck augment --target choices), or, for a card table’s rows, the other rows of the same column: a cloze card asks you to pick its gap, a line card to pick the whole sequence in the right order. Only recognizable cards (the ones with a buildable pick) are scheduled, so a Recognize session never falls back to a plain reveal, which would just be a Recall in disguise. Options are never sampled across unrelated cards; a table column is the one sanctioned pool, because its rows answer the same question by construction. A deck with no authored options, no cached distractors, and no table has nothing to recognize: the picker greys the Recognize depth out until a card carries options or you run the augment. A correct pick marks the card recognized; a quiet “I guessed” link right after lets you undo that, re-queuing it. A wrong pick shows which option was right, then Continue re-queues it too.
  • Recall (the default): the classic flashcard. Bring the answer to mind, reveal it, and self-grade. Its own FSRS schedule.
  • Reconstruct: produce the answer in full, on its own independent FSRS schedule per card. Recall and Reconstruct are two separate practices, so a card can be due for one and not the other; the one pass-only downward credit between them is covered in Scheduling.

Nothing climbs or descends between depths on its own: a card’s Recall and Reconstruct schedules just sit there side by side, and which one you exercise is entirely your call each time you start a session.

What you actually get: reveal + depth combined

The check derives from the reveal-method and the depth:

  • At Recall, a flip or cloze card reveals and you self-grade; a line card reveals line by line, then you self-grade.
  • At Reconstruct, you produce it: a cloze card has you type the gap; a card with a short, single-line answer has you type it; a line-reveal card has you type each line in turn; a card with a richer, multi-line answer becomes an explain prompt whose back lines are the key points you self-grade against.

A typed check normalizes both sides (case, whitespace, trailing punctuation) and compares exactly, with no edit-distance tolerance, then shows the diff. The automated comparison is evidence, not the verdict: grading is still yours, so a mismatch you recognize as a typo (not a wrong answer) can still be graded Got it.

Grading is always the same three (missed it / partly / got it), feeding FSRS Again / Hard / Good. See the scheduling chapter for how Recall and Reconstruct’s independent schedules work, and how badges summarize a deck’s progress at each depth.

Math during review

The adult web app, kids web app, and mobile app all display the same Rust-rendered SVG for authored LaTeX math. Inline formulas follow the text baseline and display formulas are centered and scaled to the card width. They inherit the current text color and add no background rectangle.

A cloze marker may sit inside math:

## Complete the identity.
$$a^2 - b^2 = \blank{(a-b)}\blank{(a+b)}$$

During review, the active hole becomes an underline and another hidden hole becomes an ellipsis inside the rendered formula. The substitution is display only and never reveals either answer. If RaTeX rejects a recognized formula, review shows its source plus “math could not render” rather than a blank or a plausible substitute.

explain: the self-graded Reconstruct check

The Reconstruct check for a rich (multi-line) answer is an open prompt: the back lines are the key points a good answer should cover, not a string to reproduce. You optionally type an explanation (never checked, just there to make you commit before you peek), reveal the points, and grade whether you hit them. It’s for cards aimed at understanding rather than exact recall, and it’s the everyday, self-graded tier beneath the AI exam (a later chapter).

## Explain why spaced repetition beats massed review.
Retrieval just before forgetting strengthens memory the most.
Spacing forces effortful recall; cramming lets you coast on short-term memory.

The reveal is a checklist by default: every multi-line explain card ticks against its own answer lines as the rubric, and the grade is derived from the coverage (all covered → got it, some → partly, none → missed it), a per-claim check rather than a gut call. alix deck augment <deck> --target keypoints replaces that rubric with model-written claims distilled from the card, which usually tick more cleanly than prose lines. Atomic-answer cards get no key points and keep the plain reveal.

A different augment target, alix deck augment <deck> --target format, instead reshapes a badly-shaped card (a list crammed into one prose answer, say) into clean display lines, non-destructively: it changes how the card is shown, not the deck file or how it’s graded.

The check badge

In the web frontend a small badge above the answer names the check you’re doing right now (flip, line, typing, typing · line, choice, or explain, optionally prefixed remediation · ), so how you’ll interact is clear before you commit. It badges the present interaction, not the depth: a Recognize pick shows choice whatever the card’s own mode is.

A brand-new (acquire) card is prefixed new · and names its on-ramp rather than a check, because no check is happening yet on a card you’re only meeting: new · choice when it offers options, new · draw on a sketch card, and new · reveal otherwise.

Draw instead of type: input: draw (web only)

input: is a third, separate axis: it changes how you produce an answer, not how it’s graded. draw swaps the usual typed/reveal input for a canvas: instead of typing (or just reading) the answer, you draw or handwrite it, then self-grade against the card’s normal reveal.

Two ways to reach it:

  • Draw-only cards. Set it deck-wide with input: draw in the frontmatter, or per card with <!-- input: draw -->, when the answer can’t be typed (a diagram, a circuit, a piece of notation). The reveal is whatever the card already uses: a ![](...) image on the answer side, or an explain card’s key points. An authored draw card always uses the canvas; the per-device toggle below can’t turn it off (you can’t type a diagram).
  • The per-device toggle. For a card that can be typed, the web ☰ menu’s Draw answers switch lets you answer on the canvas anyway, for the retention of writing by hand, without changing the deck file. It’s remembered per browser.

Grading a draw card is entirely self-reported: there’s no OCR or vision model reading the canvas, so it works like a self-graded flip/explain card. You judge your own drawing against the reveal. In this version input: is honored on self-graded checks only (a flip reveal or an explain); it’s ignored elsewhere.


To drop a card mid-session, press the remove key (Ctrl-X by default) instead of grading it: it leaves the session and is deleted from the deck file when you finish.

5 · Scheduling, retirement & completion

Spaced repetition is really just bookkeeping: each card remembers how well you know it and when to show it next. This chapter is that bookkeeping: the scheduler, retirement, and how a whole deck reaches “done.”

FSRS

alix schedules with FSRS, the Free Spaced Repetition Scheduler (FSRS-5, via the rs-fsrs crate). There’s one scheduler and nothing to choose: FSRS keeps a small memory model per card (its stability and difficulty) and, from your grade, works out when the card is next due.

Grading feeds FSRS a rating:

  • failedAgain, a lapse; the card comes back soon and its interval shrinks
  • partlyHard, a weak success; a shorter next interval than a clean pass
  • passedGood, the interval grows

So a card you keep getting right stretches to longer and longer intervals, a miss pulls it back in, and a partly (you got the gist but stumbled) lands in between. Early on the first successful reviews are minutes-to-hours apart (FSRS’s short-term learning steps); a card graduates into the review phase (where intervals grow to days, then weeks) only after two spaced correct recalls, and missing it resets that progress, so a slip doesn’t shortcut it.

A session shows each due card once. Miss one and it returns spaced (after its short step, interleaved behind other cards) not drilled again the instant you saw the answer (which would test your working memory, not your recall). That gap is floored at the moment you actually move off the card, not when you first saw the answer, so time spent on the feedback screen or working the next card still counts against a short retry interval. When nothing is due right now the session ends; a card still cooling is picked up the next session, or slots back in on its own if you leave the window open.

One knob shapes the whole schedule: retention, the recall probability FSRS aims for (0.70–0.99, default 0.9). Raise it to see cards more often, lower it to stretch the gaps. Set it in the [review] config section, or per workspace in an alix.local.toml (see Configuration).

A workspace deadline

Set a personal deadline on a workspace (Workspaces) and scheduling leans toward it while the date hasn’t passed: FSRS intervals cap at the days left (floored at one day), the target retention ramps up linearly to a fixed 0.95 over the last deadline_ramp days (never lowering a higher personal retention), and a due-date ceiling keeps anything from being scheduled past the deadline day. Once the date passes, both the cap and the ramp lift and scheduling releases back to your base pacing automatically.

New cards: an attempt before they’re tested

A card you’ve never seen isn’t quizzed cold (you can’t reconstruct what you’ve never read) but it isn’t simply handed to you either, whichever depth you’re reviewing at. The first encounter is a low-stakes attempt, then the answer, then one key (Seen) records it without a grade. Usually: the front shows first, you try, then reveal. On the web you can then hide and show the answer again (h, or a tap on it) to self-test the fresh encoding before you press Seen; it flips only the answer’s visibility, so the note, the buttons, and the layout stay put. If the card has authored choice options, or the deck has AI distractors (alix deck augment --target choices) and the card is atomic (single-line answer), it instead greets you as a multiple-choice question: pick one, see which was right. Either way a guess never marks it recognized or punishes it, and the first graded quiz then comes back later in the same session (once a settle gap passes it resurfaces, interleaved behind the other cards you’re seeing), so seeing a deck flows straight into drilling it. That gap is acquire_cooldown in the [review] config (default "5m"); it also sets the floor before any just-seen card (a miss, a wrong pick) may return, so nothing you moved off comes straight back. "0" disables both gaps. If the gap passes while you are sitting on the session summary, the summary says so and arms Continue; it never starts the next card for you. Each sitting serves up to max_session cards (default 10); its new-card share is new_cards_percent (default 30%, so three of ten) and the rest are due cards, with whichever pool runs short letting the other fill the cap (a fresh deck fills entirely with new, a deck with nothing new fills entirely with due). A larger backlog therefore slows introductions proportionally, so chain another sitting when a deadline is near. Both keys live in [review] (see Configuration; a workspace can override them in its alix.local.toml), and --session N overrides max_session for one launch. This holds at every depth, Recognize included: a Recognize sitting splits the same cap between never-met cards and met-but-unrecognized ones, and the met sweep finishes across chained sittings. This is the first step of a card’s life: acquire, then let its depth(s) schedule it.

Session depths: Recognize, Recall, Reconstruct

FSRS decides when a card is due; the session depth decides how deeply it’s asked when it comes up. A session runs at one of three independent depths, picked when you start it (the web picker’s Depth… menu). See Reveal & session depths for the full check matrix. In short:

  • Recognize has no FSRS schedule at all: just a boolean recognized flag.
  • Recall and Reconstruct each keep their own FSRS schedule per card, so a card can be due for one and not the other; nothing cross-credits between them, with one downward exception below.

Nothing climbs or descends between depths on its own. A card doesn’t get harder over time just by surviving reviews. Which depth you exercise, and when, is entirely your call each session.

The exception flows downward, and only on a full pass: get a card fully right at Reconstruct (in cram: only when it was due) and that also counts for its Recall schedule: if you can produce the answer, you can certainly recall it, so alix won’t re-ask the easier form days later. If recall was due at that moment, the pass stands in for that review: full schedule credit, recorded in the card’s history and marked as propagated. If recall existed but wasn’t due yet, only its due date is pushed out from now (memory untouched, nothing recorded, the same refresh a cram pass gets). A partly or a miss never propagates, and a card drilled only at Reconstruct never gains a recall schedule from this. Separately, any full pass at any depth (cram included) marks the card recognized if it wasn’t yet.

Badges

A deck can earn a badge at each depth, shown in the picker: a quick read on how solid it is, never a gate on anything (only passing the AI exam unlocks a dependent deck). A deck earns a depth’s badge once every one of its cards is currently solid at that depth: recognized, for Recognize; at or past 21 days of FSRS stability, for Recall/Reconstruct, in practice a few weeks of regular drilling. Only the highest badged depth shows: solid while the deck still clears the bar, dotted once a card has since lapsed below it (a badge, once earned, keeps its date, a high-water mark, not a live pass/fail).

A deck shows a small “new” chip while any of its cards has never been presented, whether or not the deck is badged. It clears only once every card has been seen at least once, so a large deck you are working through keeps the chip until you reach the end of it: the chip answers “is there anything in here I have never met?”, not “have I started this deck?”.

Retiring cards

A card doesn’t stay in rotation forever. Once its interval grows past retire_after (default one year), the card retires: it rests and is no longer scheduled, not even under cram, until you alix reset it. Set retire_after = "never" to keep drilling a deck forever (facts you never want to risk forgetting); a workspace can override it in its alix.local.toml.

Completion states

A deck’s state is derived from how far its cards have progressed, and shown in the picker and alix stats:

  • not started: you haven’t reviewed any card yet
  • started: somewhere in between
  • finished (done ✓): every card has graduated (reached FSRS’s review phase, past the initial learning steps)

A deck that declares a source: adds one state in between: exam due. For those decks, drilling the cards no longer finishes them: passing the AI exam does, which marks the deck mastered. That’s the subject of a later chapter.

Unlocks, in one line

Completion also drives dependencies, with no extra syntax: a deck’s exam is locked while any of its sourced prerequisites hasn’t passed its own exam. The deck itself stays drillable throughout. Passing a foundation’s exam unlocks the exams that build on it. The lock is advisory and recomputed live. The dependencies chapter covers it in full.

Cramming

Need to review everything now, schedule be damned, the night before an exam? Cram ignores due times and shows every card that isn’t retired. It’s a per-launch tick-box in the picker’s Depth… menu (key c while the menu is open); plain Learn never crams. At Recognize, cram is the repeatable quiz: it serves every card you have met, including the already-recognized ones a normal Recognize session would skip (the sitting stays bounded by max_session).

Cram changes which cards are queued, never how a due card is graded: a card that was genuinely due grades exactly like a normal review: full schedule credit, recorded (a due Reconstruct pass even propagates to Recall as usual). Only a pass on a card that wasn’t due yet is treated as the low-information event it is: its due date re-anchors by the current interval, memory untouched, nothing recorded, so a heavy grind can’t inflate your long-term spacing. A card you miss under cram always lapses normally. Retired cards stay out (that’s what retirement is for).

Review order

By default your due cards come up in scheduler order: soonest-due first. That’s right for retention, but it can feel random: a card about parsing, then one about persistence, with no thread between them.

A review order gives the session a thread. alix deck augment <deck> --target order asks the model to read the deck and lay out a graph of how the cards relate: a suggested walk through them, plus a few coarse named regions (stages or themes). It’s cached in the deck’s shareable augmentation document alongside distractors and notes; a deck can hold several, one per --with principle:

alix deck augment internals.md --target order
alix deck augment capitals.md --target order --with "north to south"
alix deck augment capitals.md --target order --with "by continent"

Then review along it: select the deck in the web picker and an inline focus drawer opens beneath it: choose which order runs the session (“Whole deck” is the default), then start.

The key thing: this changes only the order, never the schedule. SRS still decides which cards are due and how they advance. The order just serves that due set in walk order instead of shuffled, so each card is a natural follow-up to the last. Not-due cards are skipped, so the session stays as short as your due pile.

As you go, a thin region breadcrumb sits above each card (e.g. Ingestion · Review Engine · Persistence · Frontends, the one you’re in emphasized) so you see where you are in the material, not just what’s in front of you. The names are deliberately coarse: they orient without giving away any card’s answer. Under each region is a card-tier heatmap: one small cell per card. Neutral means untouched; grey means seen (the card was shown to you at least once, right or wrong); white means acquired (you got it right at least once); a learned (graduated) card is green, yellow, or red by how well you’d recall it right now; purple means retired. A region visibly greens up as you master it, and the breadcrumb doubles as a progress map.

To drill one weak region on its own, tap its heatmap in the focus drawer to scope the launch to it.

SRS still chooses what’s due within that region. You’ve just narrowed the session to it.

The choice is made before the session; the in-card breadcrumb itself stays read-only. The breadcrumb, the heatmap, and the ordering are all in place; richer map views are still to come.

6 · Cloze, dual-direction & image cards

Three extensions to the basic card, each a small addition on top of the format from chapter 3.

Cloze cards: fill in the blank

A cloze card hides part of the answer; you create one by wrapping the hidden text in \blank{...}.

Wrap any span of an answer in \blank{...} and the card becomes a cloze: each \blank{...} is a blank, and the card expands into one sub-card per blank. No directive is needed; the marker itself is the trigger.

## Complete the Rust declaration
let \blank{mut} x: \blank{u64} = 0;

This makes two cards. One blanks mut and shows the rest; the other blanks u64. The asked blank shows as ____; the other blanks are hidden as […], so no card gives away its siblings’ answers. You only produce the hidden text.

Braces outside a \blank{} are ordinary text, so let p = Foo {}; is fine in a cloze answer. If you need a literal brace inside a \blank{...}, escape it as \{ or \}.

alix keeps a card’s cloze siblings apart in the queue when other cards are available, so you don’t see mut right after u64. Editing is safe: identity is the card’s token (chapter 3), so rewording the question, or a hole’s text, keeps your history.

Reach for cloze when the context is the cue: a definition with its key term removed, a line of code with the operative token blanked.

A blank inside $...$ or $$...$$ is a piece of the formula, and is treated as one. It reveals typeset ($x = -b \blank{\pm} \sqrt{d}$ shows ±, not the characters \pm), and at Reconstruct it is sketched rather than typed, since a formula’s piece has no keyboard spelling. Write input: type on the card or the deck to keep the keyboard: an authored input: always wins, and the rule only fills in where you said nothing. alix doctor warns when a hole that stays typed holds a LaTeX command, since \blank{\pm} then asks for the spelling of \pm rather than for the sign.

Dual-direction cards: direction:

Reviewing a card both ways is what you want for vocabulary and other reversible facts. Set it per card with <!-- direction: both -->, or deck-wide with a direction: line in the frontmatter:

## purported
angeblich
<!-- direction: both -->
  • both makes two cards: purportedangeblich and the swap angeblichpurported.
  • reverse keeps only the swapped one.
  • forward (the default) is the card as written.

The two directions get distinct progress, are kept apart in the queue, and are removed together; the reversed card keeps the note. It’s best for single-line cards, and it doesn’t apply to cloze cards. When a reversed card’s question side comes from several answer lines, they render as separate centred lines rather than running together.

Image cards

Write a standard Markdown image where you want one to appear, and its position decides the side: an image in the question is a front image, one in the answer is a back image, and a card can carry more than one per side.

A one-line front needs a blank line before the --- divider to carry an image (otherwise the divider is just more content, and the image lands on the back):

## What phase is the moon in?
![](moon-waxing.png)

---
Waxing gibbous

## Play this chord:
G major
---
The open-position shape.
![](g-major-tab.png)

An image src is a path relative to the deck file, exactly the way a standard Markdown viewer resolves it: a bare filename means the image sits next to the deck, and sub/moon.png means a subdirectory. An absolute path is used as-is. The brackets can carry alt text: ![the open-position shape](g-major-tab.png).

Because the paths are ordinary Markdown, the same deck renders identically in the web app and in any Markdown viewer that opens the file directly (GitHub, Obsidian, a plain preview pane). alix doctor warns about an image file it can’t find, but doesn’t fail on it.

Source citations

A plain fact card can show where its answer comes from. Declare the deck’s source with a source: line in the frontmatter, give the card an <!-- at: ... --> locator into it, and on reveal the card offers to swap the worded answer for the exact source lines:

---
source: src/string.rs
---

## What does the `String` struct hold?
A `Vec<u8>` (its bytes).
<!-- at: src/string.rs:1-3 fingerprint: xxh64-0123456789abcdef -->

The locator is the same shape a trace checkpoint uses. Its fields are named and ordered: at: is the source path and line range (e.g. src/string.rs:1-3, just lines when source: is a single file, or a range-less path or URL to cite the whole source, the form a frozen URL source uses), and fingerprint: is an xxh64-<hex> digest of the displayed source text. alix writes the fingerprint when it creates a cited card or when you explicitly repair a hand-authored citation. A fact card may repeat the whole directive when its answer rests on several disjoint source ranges:

<!-- at: src/state.rs:64-74 fingerprint: xxh64-0123456789abcdef -->
<!-- at: src/state.rs:114-118 fingerprint: xxh64-123456789abcdef0 -->
<!-- at: src/state.rs:152-158 fingerprint: xxh64-23456789abcdef01 -->

Each locator remains one contiguous range; separate directives never imply that disjoint code is adjacent. On reveal a </> marker appears on the answer: click the answer (or press s) to swap it for the same editor-style source panels used by trace walks, and back. Multiple excerpts are stacked in authored order inside the one scrollable answer region. For a live citation, alix shows the lines only when their fingerprint still matches. A moved, changed, deleted, ambiguous, or unfingerprinted excerpt shows a warning instead of unrelated lines, without hiding the other citations. Short evidence keeps the answer’s centered vertical alignment; long evidence aligns to the top and scrolls.

This is the same machinery trace walks use to reveal source, brought to ordinary fact cards. Like every directive, <!-- at: --> is not part of a card’s identity: adding a citation never resets its progress.

You rarely write these by hand. Generating a deck from a local source (alix generate <path>) cites the lines each fact came from and fingerprints every citation. Plain alix doctor reports missing fingerprints and fingerprint drift without writing. After reviewing the cited text, alix doctor <deck> --repair-source-locators stamps a missing fingerprint or rebases a uniquely relocated exact excerpt; changed or ambiguous excerpts remain untouched for semantic review. Initializing a workspace member goes one further and freezes its source evidence and local images below assets/deck-<token>/, so the deck travels without the original source and the quotes never shift. Freezing stores only the cited excerpt, never the whole file, and leaves the deck’s source: pointed at the real material: each frozen citation keeps its real path in at: and gains an asset: field naming the content-addressed object, so a drift check can still compare against the live source when it is reachable.

7 · Directives reference

Every card marker and deck/card key in one place. Scope is where each may appear: a deck key is a line in the frontmatter (the ----fenced YAML block at the top of the file), a card key is a <!-- key: value --> comment after a card’s front, and deck · card keys work either way, with the card one taking precedence. Each links to the chapter that explains it in full.

TokenScopeWhat it does
## frontcardStarts a card at column 0; the lines below are the answer. → ch 3
> linecardA note, shown after you answer. → ch 3
<!-- -->anywhereA comment with no recognized key: ignored.
format-versiondeckThe deck format’s version, not the deck’s own. Written by alix deck init above id, stays 1, and any other number is refused rather than guessed at. Mandatory once a deck has an id. → ch 3
iddeckThe frontmatter deck ID (deck-<token>) marks an initialized deck and authorizes maintenance of missing card IDs. Its deck- prefix is what tells alix’s decks apart. → ch 3
idcardThe HTML-comment card ID (card-<token>) anchors review history. It is minted by alix deck init or a deck-creation workflow and maintained by alix, never hand-authored. After a card table it is the table’s container ID; each row’s card composes it with the row stamp (r:) in the row’s first cell. → ch 3
revealdeck · cardHow the answer is uncovered: flip (default) or line. Cloze is triggered by \blank{...} markers, never by a reveal: value.
orderdeckCard order: scheduled (default) or sequential. → ch 5
inputdeck · carddraw: answer on a canvas instead of typing. → ch 4
directiondeck · cardReview direction: forward, reverse, both.
samplingdeck · cardon (default) or off: whether a card table’s rows may draw Recognize options from their own column. A table’s value overrides the deck’s in either direction.
strictnessworkspaceExam grading rigor for the members, in alix.toml’s [defaults] only: a learner setting, so a deck declaring it gets an unknown-key lint.
requiresdeckPrerequisite deck that gates unlocks (repeatable).
authorsdeckWho made the deck: one value or a list. Holds people and any AI that helped, so there is no separate generated-by key. Yours to fill in; alix never rewrites it.
licensedeckThe deck’s licence, a single string, by convention an SPDX identifier.
tagsdeckFree-form labels: one value or a list.
created-atdeckWhen the deck was made, a single string, by convention an ISO 8601 date. Stored verbatim and not validated.
linkdecktutor reference URL, tutor-only (repeatable).
sourcedeckExam ground truth: a YAML list of URLs, files, or directories (one entry is the norm), also a trace’s cited path and a tutor reference. It identifies evidence but never grants access to a wider local tree. A workspace alix.toml may declare a source too, as supporting context for its members.
tracedeckWhat a trace walks; its presence makes the deck a trace.
atcardA repeatable named-field locator into the source (at: file:lines fingerprint: xxh64-..., plus asset: once frozen; a range-less path or URL cites the whole source): a trace checkpoint’s reveal target, or a fact card’s source citation shown on reveal.
givencardA trace checkpoint’s off-screen symbol, as name - meaning (repeatable).

Media (images, and later audio/video) isn’t a directive: write a standard Markdown ![alt](src) where you want one to appear, and its position decides the side. See Image cards.

Two that look similar but aren’t. Both point at material a deck is about, but source is the exam’s ground truth: questions are generated from it and answers graded against it, and a URL source doubles as a tutor reference. link is only a tutor reference and never becomes exam material; use it for supplementary reading the exam should ignore. The implication runs one way: a source URL is offered to the tutor, but a link is never promoted to a source.

Precedence

Where a directive can come from several places, the more specific wins:

card <!-- --> > deck frontmatter > workspace [defaults] > built-in default

So a card’s reveal directive overrides the deck’s, which overrides a workspace’s [defaults], which overrides alix’s default (flip).

The session depth (Recognize/Recall/Reconstruct) is not in this chain either: it isn’t config or a deck directive at all. It’s chosen per session (the picker’s Depth… menu), the same way for every deck (see Reveal & session depths).

8 · Workspaces

As your decks multiply, you’ll want to treat a cluster of them as a unit: all your Spanish decks, or every deck about one codebase. A workspace is that unit: a folder of decks reviewed together, sharing settings and a name, with its own progress.

Making a workspace

A workspace has an alix.toml at its root and its initialized .md decks as direct children of decks/. The manifest is a scoped version of the global config file. It sets a title and a [defaults] table of directives that every member deck inherits:

# ~/decks/spanish/alix.toml
title = "Spanish"

[defaults]
direction = "both"
reveal = "line"

Besides title, description, icon, and a shared source, the manifest may set a top-level source_access, which overrides the global [ask] source_access for this workspace’s decks in either direction (see the tutor). The manifest travels with the folder when shared, so review a received workspace’s alix.toml before an AI call.

Starting from nothing instead? alix workspace init <dir> (--title to name it) scaffolds an empty workspace: an alix.toml, an alix.local.toml, and an empty decks/ plus assets/. Both TOML files come fully commented, each key explained inline, so they document themselves. The fixed layout is:

spanish/
├── alix.toml
├── alix.local.toml
├── decks/
│   └── verbs.md
├── assets/
│   ├── icon.svg
│   └── deck-<token>/
│       └── sha256-<digest>.<ext>
├── progress/
└── augment/

Grow the workspace with alix generate … --workspace <dir> or alix deck import … --workspace <dir>, also available from the web UI’s ☰ menu’s Add deck… sheet. Dependencies (requires:) are still edited by hand in the deck files.

Put hand-authored decks under decks/, then run alix deck init <file> once for each one. Markdown without a valid opening-frontmatter id: deck-<token> is ignored by discovery. Root-level Markdown is never a workspace member, so README-style prose and notes can live beside alix.toml without becoming picker entries or being stamped.

Initialization also makes the member portable. Cited excerpts are copied from explicit source files and source directories alike (never a whole file or repository), and local card images are copied into assets/deck-<token>/. Every managed filename is the SHA-256 address of its exact bytes. The deck is not initialized successfully if required evidence or an image cannot be copied.

Updating from the live source

Frozen evidence is deliberately stable. It does not follow later source edits in the background. Reconcile every frozen source-backed member explicitly:

alix workspace update ~/decks/spanish

The command gives its AI backend read-only access to each recorded local source, then writes one exact proposal into a dot-prefixed sibling workspace. The original workspace remains untouched. Inspect the proposed decks and evidence there, then publish those exact bytes without another model call:

alix workspace update ~/decks/spanish --apply

Use --discard instead to remove the proposal. Apply refuses if an original deck changed after staging.

A card ID belongs to one learning proposition. An unchanged question and answer may keep its ID while its note or source locator improves. If the question, answer, cloze, or learning image changes, the old card and ID retire together and the replacement receives a fresh ID during staging. Obsolete cards are removed rather than rewritten in place under their old learning history.

The first update implementation accepts local file and directory sources. A remote URL source remains review and tutor context, but cannot yet be captured as a new portable snapshot.

Moving decks between workspaces

A workspace deck owns more than its Markdown file. Transfer it with Alix so its frozen evidence and augmentation follow the stable deck ID:

alix deck copy ~/decks/spanish/decks/verbs.md ~/decks/exam
alix deck move ~/decks/spanish/decks/verbs.md ~/decks/exam

Both commands preserve the filename, deck ID, and card IDs. Copy installs the same public bundle that wormhole sharing sends: the deck, assets/deck-<token>/, and augment/deck-<token>.json. It never copies progress. Move requires confirmation, installs that public bundle first, carries progress/deck-<token>.json when the workspaces use different user roots, then removes the source. Workspaces configured to use one shared user root already address the same progress document by deck ID, so no progress file moves.

The destination must be another Alix workspace. Transfer refuses overwrites, stable-ID collisions, missing required decks, and moves that would break a source deck’s dependents. An inherited or relative source is written explicitly into the transferred deck so the destination cannot reinterpret its live provenance through unrelated workspace defaults.

Now open the cluster and drill its members one at a time:

alix ~/decks/spanish/

Shared directives

The [defaults] keys are the deck-directive names reveal, input, order, direction, and sampling from the deck format, plus strictness: the learner-side exam rigor, which a deck itself cannot declare. They fill in only what a deck doesn’t set for itself, so the precedence is one level deeper than before:

card <!-- --> > deck frontmatter > workspace [defaults] > built-in default

Set direction = "both" once for the whole folder, and a single irregular deck can still override it with its own direction: forward in its frontmatter. It’s the same directive system from chapter 3, just sourced from one more place.

Personal pacing: alix.local.toml

The alix.toml is shared: it travels with the workspace when you hand it to someone. Your personal review pacing doesn’t belong there. Drop an alix.local.toml beside it to override the global [review] config (FSRS retention, retire_after, acquire_cooldown, and the pacing keys max_session / new_cards_percent) for this workspace’s decks only:

# ~/decks/spanish/alix.local.toml
[review]
retention = 0.95         # see these cards more often
retire_after = "never"   # never let them retire
max_session = 20         # bigger sittings for this deck
new_cards_percent = 40   # lean harder on introducing new cards
deadline = "2026-09-01"  # a personal "ready by" date, the day itself inclusive
deadline_ramp = "14d"    # how early the pre-deadline retention ramp starts

It uses the same [review] keys as the config file, and it’s kept separate from alix.toml on purpose, so it stays yours and never travels when you share the workspace. A missing or malformed one is simply ignored.

deadline and deadline_ramp only take effect inside a real workspace (a directory with an alix.toml). Set them on a plain decks folder, or on a loose deck’s alix.local.toml, and they parse but do nothing: no scheduling ramp, no picker readout, no doctor warning. See Configuration for the full reference and Scheduling for what the ramp does to review.

The session depth (Recognize/Recall/Reconstruct) isn’t a workspace setting. It’s picked per session, the same as for a loose deck (see Reveal & session depths).

Its own files

A workspace keeps shareable material at the workspace root and private learning state in its selected user-files root:

augment/deck-<token>.json    # shareable generated choices, notes, and topologies
assets/deck-<token>/         # shareable frozen excerpts and local images
progress/deck-<token>.json   # private schedules, history, exams, virtual cards

Renaming a deck file leaves these paths unchanged because the name comes from its deck id (deck-<token>), not its display name. By default the private files are colocated with the workspace, so folder synchronization carries progress too. A store = "..." line in alix.toml moves only private files such as progress/ and recent.json; augmentation and assets stay beside the decks they describe.

That makes a workspace a self-contained, portable unit for moving, backup, and folder synchronization: authored decks in decks/, frozen excerpts and images in deck-owned assets/deck-<token>/ directories, workspace icons directly in assets/, and shareable augmentation all live under one boundary. Sharing strips progress and local configuration while carrying the matching augmentation and assets. Decks outside any workspace keep shareable material beside the deck and private files in the selected user-files root. The CLI commands (alix stats/list/reset) take a deck file, a plain folder, or a workspace: a folder or workspace expands to its member decks, each resolved against the same user-files root the launcher would use (--store <path> still overrides private files only).

In the picker

Folders show up in the picker in two flavors: a folder with alix.toml and initialized decks/*.md members appears under Workspaces; one without a manifest is a plain Folder whose initialized decks are direct *.md children. Opening either drills in to its decks, drawn as a dependency tree: each deck nests under the prerequisite that gates it, foundations at the roots (the next chapter). A trace member carries a trace badge (facts decks are unbadged), and the drill-in is a single-launch list: Enter on a facts deck reviews it, Enter on a trace walks it. Typing a filter flattens the tree to a plain search.

In the web picker, a workspace can show a small emblem in place of the chevron, so a long list of similar-named workspaces is quicker to scan. Drop an image in the workspace’s assets/ and point icon = "assets/<file>" at it in the alix.toml (or just name it assets/icon.{svg,png,jpg} and skip the key); an SVG is tinted to the active theme, a raster shows as-is. When you build a workspace with alix generate <source> --workspace <dir>, the model draws an abstract SVG emblem from the topic automatically, unless you pass --icon <file>.

alix <dir> serves a workspace directly: the picker opens drilled into that view, scoped to the folder and its own store, routing each member to the right experience (a facts deck to a review, a trace to a walk) and returning you to the picker when you finish one. (A session is one deck file, so a whole workspace is never reviewed at once; open it and pick a member.)

A folder without a manifest serves the same way with alix <folder>; it just applies no shared directives.

Sharing a workspace

A workspace is a self-contained folder, so sharing one is sending the folder with its decks/ structure intact. alix share <dir> does that over magic-wormhole with the personal files (progress/, backups, recent list, alix.local.toml) left home; the other side runs alix receive <code> and gets it beside their own decks, ready to serve with alix <dir>. Precomputed augmentation documents matching the shared decks travel: the AI content comes along, unrelated augmentation and progress do not. A single-deck share carries the .md member, its complete assets/deck-<token>/ directory, and its matching augmentation. Also available from the web UI’s ☰ menu (Share… / Add deck… → Receive), with a .zip download/upload fallback when neither side has wormhole installed.

Titles

A single deck’s display name is its # heading (the top-level Markdown title); a workspace’s name comes from a title in its alix.toml. Either replaces the file name in the picker, the session header, alix list, and alix stats. It’s display-only: you still refer to decks by file path on the command line, and a title never affects a card’s identity.

9 · Dependencies & unlocks

Real subjects have an order: you can’t grasp borrowing before ownership, or a derived rule before its axioms. alix lets a deck declare what it builds on, and uses that both to sequence your study and to gate decks until you’re ready.

Declaring prerequisites: requires:

A deck names its prerequisites with a requires: list in its frontmatter (repeatable):

---
requires:
  - rust-ownership
  - deck-9w2c7x4k1m8q3z5t0v6b2n4d8f
---

## What does the borrow checker prevent?
Aliasing a value while it's mutably borrowed.

Each entry is a filename or a deck id. A filename (rust-ownership, with or without the .md) resolves next to the requiring deck or in your decks directory. A deck id (deck- followed by a 26-character token) resolves to the deck that carries that id wherever it lives, so the edge survives the prerequisite being renamed. An entry counts as an id only when it is exactly deck- plus a 26-character canonical token and nothing else; any other value, including a deck-… name written with the .md extension or a ./-prefixed path, is read as a filename. Prefer the id form for a rename-proof edge, the filename form for readability. Directives aren’t card content, so adding or changing them never touches card progress. A missing prerequisite or a dependency cycle is treated as non-blocking. A broken edge never hides a deck.

Dependencies don’t change what you review

requires: is about order and gating, not session contents. When you review (or browse) a deck, the session holds exactly that deck’s cards; prerequisites are never pulled in, so the reveal/order you study under is always the deck’s own. What dependencies shape is the picker’s dependency tree (foundations shown first) and, for a deck with a source:, the exam gate below.

Unlocks

The same requires: graph drives unlocks, with no extra syntax, and the gate is the exam, not drilling. You can review any deck at any time, in any order; what requires: controls is exam order: a deck with a source: can’t sit its exam until each of its sourced prerequisites has passed its own exam, and passing a foundation’s exam unlocks the exams that build on it. A prerequisite with no source: at all has no exam to pass, so it never gates: its edge is just a suggested order in the tree. (alix doctor warns when an exam-grounded deck requires one without exam grounding, since that edge can’t gate an exam; add a source: to the prerequisite to make it real. It also flags a dangling requires:, one naming a deck that does not exist, so a renamed or deleted prerequisite is caught rather than silently dropping the edge, and distinguishes an id-mode value that points nowhere from a card id pasted by mistake or a file that merely shares a required id’s name; see the doctor reference.) (A trace masters by passing its exam (retracing the path from memory) so it gates and unlocks like any exam-grounded deck.)

In the picker a deck whose exam is locked shows a 🔒, but it stays drillable: only the exam waits on the prerequisites.

This is what turns a folder of decks into a curriculum: order the material by requires:, and alix gates each step’s exam on passing the last. It’s the backbone of the AI exam’s notion of mastery (a later chapter) and of how alix generate lays out a generated learning plan.

10 · The Tutor

This is where the AI layer begins. Everything so far (drilling, scheduling, workspaces) runs entirely offline. From here on alix shells out to the configured model CLI, and the first place it does is the most useful: a tutor on any card.

(One reminder: every AI feature shells out to the configured model CLI, so it needs the CLI installed and logged in. See chapter 2. The flashcard core never calls it.)

Asking about a card

On any post-answer screen (a revealed flip card, the feedback after a typed answer, an answered choice) an Ask button (or the ? key) opens a chat panel without leaving the session: type a question, Send, Make this a note, Close. alix hands the tutor the card (its front, answer, note, and deck name) as context, and you can ask “why is that the answer?”, “what’s a simpler way to see this?”, or anything else, and follow up. The server runs the model CLI on a background thread and the page polls for the reply, so the single-threaded server never blocks and the session stays responsive while it works.

In the web panel, Enter inserts a newline and Shift-Enter sends. Closing a tutor that contains a conversation asks for an explicit click on Leave anyway; Escape chooses Stay so it cannot also abandon the card.

One conversation spans the whole review run. For Claude, alix uses --session-id for the first question and --resume for each follow-up, so the model remembers earlier cards and questions efficiently. Other backends re-inline the accumulated Q&A transcript into each prompt, so the context carries over, at the cost of a growing prompt rather than a resumed session. Either way you can ask how the current card relates to one from ten minutes ago, and the tutor knows.

Ask is available wherever you serve, including over --lan, but the request runs the model CLI on the host machine, so, like --lan in general, only enable it on a network you trust.

Saving what you learn: Ctrl-N

When an exchange clears something up, press Ctrl-N: the tutor condenses the conversation into at most three short note lines and appends them to the card in its deck file. Notes aren’t part of the card’s identity, so its progress is untouched: you just keep the insight. (In the web panel, Make this a note does the same.)

A deck can point the tutor at background reading with a link: list in its frontmatter:

---
link:
  - https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html
  - https://tokio.rs/tokio/tutorial
---

These are handed to the tutor with your first question as material to consult when useful: fetched once and remembered for the rest of the run. They’re tutor-only: unlike source: (the exam’s ground truth, covered next chapter), a link: never becomes exam material. And like every directive, they don’t affect a card’s identity.

Grounding a frozen card: source:

A frozen workspace card is grounded in its deck-owned assets/deck-<token>/ evidence. The tutor receives the exact excerpt shown during review, so deleting or editing the live source cannot silently change its ground truth.

The deck’s source: (and a workspace’s source) records where that evidence came from and can give the tutor broader current context. A URL source is fetched when the selected backend can use WebFetch. A local source is readable only when [ask] source_access = true. The tutor always receives the frozen excerpt first; current source context can explain the surrounding material or detect drift, but never silently replaces the captured evidence.

Local file grounding is opt-in with [ask] source_access = true, and a workspace’s alix.toml may carry its own top-level source_access key, which overrides the global setting in either direction for that workspace’s decks. The manifest travels when a workspace is shared, so inspect a received workspace’s alix.toml before making an AI call over it. An explicit deck or workspace source defines the readable root. Without one, alix does not grant the tutor filesystem access: a source: identifies the cited evidence, but it never implicitly authorizes the surrounding project. This keeps decks portable across profile-managed deck directories and keeps every wider live-source grant reviewable: globally in your config, or per workspace in a manifest you can read.

When no usable source is available, the tutor still works from the frozen excerpt and card context. The Ask status warns that it lacks the full current source, so the learner can distinguish an evidence-grounded explanation from a freshness check against the live source.

How it’s sandboxed

Because the CLI runs headless, it can’t show interactive permission prompts: an unanswerable prompt would just hang the call. So alix runs it locked down with a locked permission mode plus an exclusive tool allowlist (WebFetch, WebSearch by default). The listed tools work without prompting; every other tool is silently denied. That means a malicious page behind a deck link can’t make the tutor run shell commands or touch your files. Both the permission mode and the allowlist live in the [ask] section of the config, along with the command, a --model override, and the timeout.

Make this a card

During an Ask exchange, if the tutor’s reply answers a question about a concept you’d like to drill, click Make this a card. The tutor distills the conversation into a draft front/back for you to edit. Once you’re satisfied, click Add to land it as a new card on the current deck.

The card is virtual until you promote it to the deck file; it lives in the review progress store but doesn’t yet appear in the .md deck. You drill it like any other card, building up history. When you’re ready to make it permanent, the Promote to deck entry in the ☰ menu (offered only while the current card is a virtual one) appends it to the deck file immediately, carrying its progress over, so future runs see it.

This is an adult-review feature only; it’s not available in the kids interface. If the tutor’s draft can’t be parsed as a valid front/back pair, alix reports the error plainly rather than inventing a card, so you can ask for a clearer format.

11 · Generating decks: alix generate

Authoring cards by hand is the slow part of any flashcard habit. alix generate removes it: point it at a source and the model drafts a deck of fact cards for you.

alix generate https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html
alix generate src/scheduler.rs   # a local file

The source is a web page URL or a local file. It’s the one AI-authoring verb: the same command with --trace builds traces from the same kinds of source, and a directory source is explored for a whole learning plan first (both later chapters). Pass --deck to force a single deck from a directory.

While the model works, alix prints short progress updates to stderr, such as source fetching, source reading, and drafting. Partial generated cards stay hidden until the complete result has passed validation. Deck drafting has a one-hour absolute limit. With a structured-event backend, every generation path also has a five-minute inactivity limit that resets on each real agent event. For a backend without structured events, that five-minute value becomes a nonrenewing absolute fallback because Alix cannot distinguish silence from work. Set idle_timeout_secs = 0 to disable either use and leave only the one-hour limit. Configure the limits under [generate]. Trace and workspace planning calls keep their absolute limit under [trace].

Also available from the web UI’s ☰ menu (Add deck…), URL sources only. See the web app.

What you get

The model reads the source and returns a deck spread across four layers of understanding (facts → concepts → application → connections) using cloze cards for terminology. The prompt has it draft, then re-read the whole set and merge or drop cards that test the same fact, so the deck doesn’t repeat itself. alix validates the text it gets back (it only ever accepts cards, never a write or shell command) and writes it to ~/decks/<slug>.md.

How the source is recorded depends on its kind, and it matters later:

  • A web page is read with the WebFetch tool, and the deck opens with a link: line back to it, so the tutor can consult the page on your cards.
  • A local source is explored read-only with Read/Glob/Grep, and the deck opens with a source: line, so the AI exam can later grade your understanding against that same source (next chapter). Each fact that maps to specific lines also gets a fingerprinted <!-- at: --> citation, so you can flip the card to its source on reveal without trusting a shifted numeric range.

Useful flags

alix generate <source> -o ownership              # choose the output file name
alix generate <source> --cards 15                # aim for at most 15 cards (a soft ceiling)
alix generate <source> --review                  # a 2nd pass that dedups and tightens
alix generate <source> --print                   # print to stdout instead of writing a file
alix generate <source> --workspace ~/decks/rust/ # write it under that workspace's decks/
alix generate <source> --goal "pass the citizenship test"
alix generate <source> --language German --audience "new voters"
alix generate <source> --card-style authored-choices

--goal controls what the learner should understand for every new deck or workspace, including a single deck generated from a URL or file. --language sets the language of fronts, answers, choices, and notes. --audience steers vocabulary, assumed knowledge, examples, and difficulty.

--card-style accepts mixed (the default), plain, cloze, or authored-choices. Authored choices use the deck’s GitHub task-list format, with one checked correct answer and unchecked distractors. Alix parses the result and refuses a generated facts deck containing a card of the wrong shape, so a model cannot silently turn an authored-choice request into ordinary recall cards. In a generated workspace the style applies to every [deck] item; [trace] items keep their predict-and-verify checkpoint shape. Goal, language, and audience apply to both.

--review runs a second model call that takes the draft and returns a deduplicated, tightened version while preserving the requested language, audience, and card style. It costs an extra call, but it’s worth it when the source is repetitive. The prompt and defaults (model, timeout_secs (default 3600), idle_timeout_secs (default 300; structured inactivity or an unstructured absolute fallback, and 0 disables), max_cards (default 100, a soft ceiling: an overshoot is kept and warned about), language, audience, card_style, and an extra instruction field) live in the [generate] section of the config.

Generate, then own it

A generated deck is just a plain-text deck like any other: read it, edit it, cut the weak cards, add your own. Treat the output as a strong first draft, not gospel. The point is to skip the blank page, not to outsource judgment. That’s the same division the whole tool runs on (see how alix was made).

12 · The AI exam

This is the feature the whole tool is built around. Drilling cards loads a deck’s material into memory; the AI exam checks that you actually understood it, and passing the exam, not merely finishing the cards, is what marks a deck done and unlocks what depends on it.

The reasoning: recall isn’t understanding. You can drill every card and still not see how the ideas connect. So a deck can name a ground-truth source and require you to pass an exam against that source before it counts.

Declaring a source: source:

Name one or more sources in the deck’s frontmatter, each a URL, a file, or a directory:

---
source:
  - https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html
  - notes/ownership.md
---

A URL source: doubles as a tutor reference, so you needn’t repeat it as a link:. The reverse doesn’t hold: a link: stays tutor-only and never becomes exam ground truth: keep supplementary reading (a blog post, an SO answer) as link: so the exam ignores it.

Once every card in an exam-grounded deck has graduated (reached FSRS’s review phase, past the initial learning steps) the deck is exam due rather than finished: drilled, but not yet counted, so it doesn’t unlock its dependents yet. A deck with no source: at all (and no workspace source) simply becomes finished when all its cards graduate, unlocking its dependents directly.

Sitting the exam

The exam is a guided, one-question-at-a-time flow (answer, move Back/Next, then a per-question breakdown) in the browser. You reach it two ways:

  • From the picker: choosing an exam due deck starts the exam instead of an empty review.
  • From the summary: when you drill a deck’s last cards and it turns exam due, the session-end summary offers it.

alix asks the model to read the source (URLs via WebFetch, local files embedded) and write fresh understanding questions (application and connections, not the card facts) each with the key points a correct answer must hit. You type a prose answer per question, and an examiner grades each Pass / Partial / Fail against the source’s rubric, never against your cards (grading the cards would be circular). The model calls run on a background thread, so the UI stays responsive while it thinks.

  • Pass (every question by default, tune with pass_threshold) marks the deck mastered (mastered ✓). Mastery, not mere drilling, is what unlocks decks that requires: this one. Source-less decks are unaffected: finishing them just means drilled (done ✓).
  • Fail lists the gaps and offers to turn them into remediation cards: a cloze card or a plain card for a missed fact, an open understanding card (a prompt plus key points) for a missed concept, with overlapping gaps merged. Re-drill those and re-sit. Once created, the screen reports how many remediation cards it added.

Those remediation cards are virtual: they live in alix’s store rather than in the deck file. While drilling one, the review screen’s mode badge gains a remediation · prefix ahead of its check (e.g. remediation · flip); its very first encounter still badges new like any unacquired card. A virtual card drills like any other (its first pass comes one acquire cooldown later, then FSRS schedules it) and it counts toward the deck’s due total but not toward the deck’s card count, and it never rewrites your .md. Regenerating the same gap won’t duplicate it; once its interval reaches the retirement cap it’s archived, and re-failing the gap brings it back. When a remediation card has earned a permanent place, promote it during review (“Promote to deck” in the browser’s review menu): alix appends it to the deck file, removes the virtual copy, and carries over the card’s review progress. It doesn’t restart.

A trace deck is examined differently: instead of generated questions, its exam asks you to retrace the whole path from memory in a sentence or two (the compression) graded holistically against the checkpoints (no question generation, no source read). Passing masters the trace; a fail sends you back to re-walk it. See trace decks for the full flow.

Resetting a whole deck (alix reset <deck>) also clears its mastered state, so a re-drilled deck must pass again; resetting only an individual card (--card) leaves mastery intact.

Strictness: match the rigor to the material

How hard each answer is judged fits the material: a checklist topic (a procedure, exact syntax, a security drill) should fail you for omitting a step; a conceptual topic shouldn’t. It’s a learner setting, not per deck: the [exam] strictness config default, optionally overridden per workspace in alix.toml’s [defaults]. The levels:

  • strict: completeness required: every rubric point must be present, so omitting one is a gap.
  • balanced (default): judges understanding, not phrasing: a point counts if your answer shows you grasp it, even briefly; only a wrong or genuinely-absent idea is a gap.
  • lenient: benefit of the doubt: only clearly wrong or unanswered points are gaps.

This dial (how hard each answer is judged) is independent of pass_threshold (how many answers must pass). Both, plus model, timeout_secs (default 300), num_questions (default 5), and an extra guidance field, live in the [exam] config section.

Why this is the centerpiece

Everything else serves this. The drilling loads the facts; the exam is the gate that turns “I reviewed it” into “I understood it, and here’s the check.” It’s also why mastery (not completion) drives unlocks: a curriculum should open the next door only when you’ve genuinely passed through the last. The everyday, self-graded rehearsal for it is explain mode (chapter 4); the exam is the real thing.

13 · Trace decks

Experimental. Traces are new and still evolving — the deck format and the flow may still change.

Cards drill facts — the nodes of what you know. A trace drills the connections between them — the edges — by walking a path through a real source and making you predict each hop before it’s revealed. Where the AI exam verifies a set of independent answers, a trace verifies you can follow one chain of reasoning, and the gap between your prediction and the truth is where the understanding forms.

This is the most direct expression of the book’s opening bet: understanding is the chain of because-this-therefore-that, and a trace makes you build that chain yourself.

What a trace looks like

A trace is a deck with a trace: (a path description — what it walks, and the thing that marks the deck a trace) and a source: (the path’s origin), then a sequence of checkpoint cards. Each checkpoint is an explain-style card — an open predict prompt and the key points a good prediction should hit — plus a <!-- at: --> locator pointing at the real lines in the source:

---
trace: how `let s2 = s1` moves a String and avoids a double free
source: .
---

## You write `let s2 = s1`. What gets copied onto the stack, and what stays shared?
Only the stack data (pointer, length, capacity) is copied.
So s1 and s2 point at the *same* heap allocation.
<!-- at: src/ch04-01-what-is-ownership.md:290-297 fingerprint: xxh64-0123456789abcdef -->
> The heap contents themselves are never copied here.

## So s1 and s2 point at one heap allocation. What breaks when both go out of scope, and how does Rust stop it?
Both would call drop on that memory (a double free).
Rust treats the assignment as a move: s1 is invalidated, so only s2 frees it.
<!-- at: src/ch04-01-what-is-ownership.md:322-343 fingerprint: xxh64-123456789abcdef0 -->
> Using s1 after the move is a compile-time error.

The trace description, checkpoint prompt, given values, key points, and note all support the same inline Markdown and LaTeX rendering as ordinary cards. Inline-code terms in a checkpoint’s key points are also highlighted wherever they occur in its revealed source excerpt. Matching is exact and case-sensitive, so the author controls the emphasis by choosing which terms to put in backticks.

The <!-- at: --> locator’s at: field is a single contiguous range file:start-end (just line numbers when source: is one file; a range-less path or URL cites the whole source, the form frozen URL sources use), never comma-separated, since a stitched excerpt makes disjoint code look adjacent. Its fingerprint: xxh64-... field fingerprints the displayed lines. A live walk reveals the source only while that fingerprint matches, so a shifted numeric range cannot silently show unrelated lines. When a tight excerpt leans on a symbol defined off-screen, name it with a <!-- given: --> line (<!-- given: state — the parser's position so far -->, repeatable); these show as a list under the question, so the excerpt stays focused without orphaning the names it needs.

Building it with the model

You don’t have to hand-write checkpoints. Declare just the trace: and source:, then name the stub deck as alix generate’s source:

alix generate mytrace.md

The model explores the source — read-only Read/Glob/Grep, source root as its working directory, no write or shell access — finds the single load-bearing path, and writes the checkpoints (with their <!-- at: --> locators) back into the deck. Alix fingerprints those locators before placing the result. The result is cached and version-controlled there, so review it (especially the locators) and edit freely; re-run it to regenerate.

Building is one-shot, correctness-critical, and fails silently when the model is weak — you still get parseable checkpoints, just a loose chain you then drill. So the [trace] config defaults the build to the backend’s strong model (for Claude, opus; set model to override) and high reasoning effort (effort = "high"): slower than the other AI features, but it runs once and is amortized over many reviews. The suggestions pass (--trace --plan, below) shares those settings; walk grading ([trace] auto_grade) does not (it’s a light per-hop call at the tutor tier).

Don’t know what to trace? — --trace --plan

alix generate . --trace --plan

does a single read-only recon pass over a source (a repo ., a directory, a file, or a URL) and prints a ranked menu of candidate traces — each a path-question, a one-line spine sketch, and a suggested source: scope. The list is sized by coverage (the central spine plus one main path per major subsystem), so it’s as long as the source needs. It also names the node-shaped subsystems it skips — a config table, a store’s on-disk format — as facts-deck material, because facts are a deck’s job and edges are a trace’s. It writes nothing: pick one, paste its header into a new deck, and alix generate it. Knowing what is worth tracing (and how deep) is the genuinely hard part — it needs you to already understand the source — so this hands that judgment to the model.

Write it as a chain, not a quiz

A trace’s whole value is that it’s a path: each checkpoint picks up where the last reveal left off (notice how hop 2 above opens with hop 1’s conclusion, “s1 and s2 point at one heap allocation”), so you follow one thread — a data flow, a control flow, a derivation — to an outcome. If the checkpoints are independent facts hanging off one thing, you’ve written a set, which is what cards and the exam already do; choose a subject with a real sequence instead.

Walking it

Pick the trace in the web picker, or on the mobile app (the walk runs fully offline there too): a trace opens as a walk: a checkpoint-by-checkpoint descent (the hop list rides the wire but is not yet rendered as a rail) with each checkpoint’s source shown in a line-numbered excerpt. It goes hop by hop:

  1. Predict — type a guess before anything reveals (committing is the point).
  2. Revealalix shows the real excerpt from the source, then the key points and note.
  3. Gap — you judge yourself Missed it / Partly / Got it (the same three grades review uses). Self-judged and offline by default; set [trace] auto_grade = true in the config to have the model judge your typed prediction against the key points and return a verdict plus a line of feedback (a model call per hop; a desktop/web setting, since the phone’s walk is always self-judged). Either way, a failed or partly hop is a weak edge that resurfaces sooner — a failed one resets, a partly shortens its next interval (FSRS Hard) — while a passed hop advances and fades. Each checkpoint is an ordinary card underneath, so this is the normal per-card SRS.
  4. Done — after the last hop the walk is complete. That’s the drill; the verification (what masters the trace) is its separate exam, below.

The exam — the compression

A trace’s trace: is a question (“how X becomes Y”). The exam is to answer it: retrace the whole path in a sentence or two, from memory. The model grades that compression against the path’s checkpoints (AI-graded, exactly like a fact deck’s exam) and passing masters the trace, which unlocks its dependents. So the symmetry is:

  • walking the checkpoints (predict → verify each edge) is the drill;
  • the compression is the exam.

You reach it in the browser: the capstone offered at the end of a walk (Take the exam?), or the picker’s “Take exam” button. A paired phone offers the same capstone from its own walk. Like a fact deck, you can sit it early to test out — gated only by requires: (a trace’s sourced prerequisites must be mastered first).

A failed trace exam is re-walked, not turned into remediation cards (a trace is a path, not a card pile) — the weak checkpoints already resurface sooner through their own SRS. After a fail the exam cools down for a while before you can re-sit it, so the graded feedback can’t simply be pasted back into the one fixed question ([exam] retry_cooldown_secs, default one hour; 0 disables it).

Immediate freezing

Because <!-- at: file:lines --> reads the live source, editing a traced file could shift every excerpt to unrelated lines. Initializing any workspace member therefore freezes its evidence immediately. Every source, an explicitly named file and a directory alike, is reduced to the excerpts cited by its cards, so Alix never exports a whole file or an entire repository.

Every copied excerpt lives below assets/deck-<token>/ and is named sha256-<digest>.<ext>, where the digest covers its exact stored bytes. Freezing leaves the deck’s source: pointed at the real material (a path or a URL): it is never rewritten to point into assets/. Each <!-- at: --> keeps its real at: path and lines, keeps its excerpt fingerprint:, and gains an asset: field naming the content-addressed object:

<!-- at: scheduler.rs:90-98 fingerprint: xxh64-0123456789abcdef asset: sha256-<digest>.rs -->

Review reveals the frozen excerpt (display evidence, numbered from the at: start line), and the fingerprint: verifies those stored bytes. The at: path and the deck’s source: retain provenance for drift reporting and a future deliberate source update. When the live source is available and permitted, the tutor and exam may consult it for surrounding context and staleness detection; offline, they report the missing live source rather than silently degrading. A loose trace over a live source is left as-is.

Checking the locators

For a trace that isn’t frozen — a loose .md over a live source:alix doctor <deck> validates that every <!-- at: --> still resolves and matches its fingerprint. A missing fingerprint, missing file, changed excerpt, or ambiguous exact match is reported without writing. If the exact text moved to one other range, doctor reports that safe rebase. After reviewing it, run alix doctor <deck> --repair-source-locators to stamp missing fingerprints and apply only unique exact rebases. Changed and ambiguous excerpts remain untouched. Frozen assets do not move, but doctor still verifies their captured text and separately reports live-source drift.

A trace deck degrades gracefully — even outside a walk it’s a valid deck of explain cards. See docs/examples/workspace-showcase/decks/ownership-move.md for a complete trace, frozen evidence from The Rust Book’s ownership chapter, so it walks offline.

14 · Generate a workspace — goals & curricula

alix generate --trace --plan lists central traces. Pointing alix generate at a directory goes a layer up: give it a goal and it explores the source first — one AI planning pass — and prints an ordered learning plan: the facts decks and traces worth authoring to reach that goal, dependency-ordered.

alix generate . --plan                                      # a plan to understand the whole source
alix generate . --plan --goal "how review scheduling works" # a narrow goal → a focused subset

Each item is tagged [trace] or [deck], chosen by the shape of the knowledge: a path you predict hop by hop becomes a trace; a table of facts — a config’s knobs, a store’s on-disk format — becomes a facts deck. Each carries its requires: prerequisites (the list is a valid dependency order, foundations first) and a source: scope. The --goal scopes coverage: a broad goal spans every subsystem; a narrow one collapses to its slice and traces it in more detail. --plan is read-only — it prints the plan and stops, so you can author the items yourself (alix generate a trace or a facts deck per item).

Building the workspace

alix generate . --goal "how review scheduling works" --workspace ~/decks/scheduling/
alix generate . --source-url https://example.com/project --workspace ~/decks/project/

Without --plan, the plan’s size decides. A one-item plan collapses to a single facts deck (--deck forces that from the start, skipping the plan pass). More items become a workspace build: the plan prints, alix confirms (Build N items into <dir>? [y/N]-y skips it), then goes all the way — it explores the source once and reuses that single session to fill every item — predict-verify checkpoints for the traces, fact cards for the decks — so the workspace comes out review-ready in one command: an alix.toml (carrying the goal; --title names it) and one file per item under decks/: a trace: deck per trace and a # facts deck per deck, wired together with requires: so they unlock in dependency order, each source: pointing back at the real source. Writing the whole set from one understanding keeps the items coherent (each builds on its prerequisites instead of repeating them). Before the hidden staging workspace becomes visible, Alix initializes every complete member and freezes its evidence. Cited excerpts (from files and directories alike) and local card images land below each member’s assets/deck-<token>/ directory. A missing or changed required source aborts publication instead of leaving a live or partially frozen workspace. --source-url <URL> records a public source in the workspace defaults. Tutor and exam calls can use it for wider context and staleness checks after the local generation source is gone, while review continues to use the frozen assets.

The destination is --workspace <dir>, defaulting to a folder named after the source under your decks directory.

Populated destinations

Pointing a build at a destination that already has files never blocks the run or loses anything: alix builds into a scratch staging folder next to it first, then moves the new member files into decks/ one by one. A name that’s already there keeps your existing file untouched — the new version stays behind in the staging folder, reported at the end so you can compare and move it in by hand — while everything else lands normally. Pass --force to overwrite collisions instead.

This is the tool’s high-water mark: name what you want to understand, and alix assembles a dependency-ordered curriculum of facts and traces — gated by mastery — that you climb.

The explore walk — --trace

Before you even know what to trace, alix generate <source> --trace builds a short tour of the source’s shape, written as a trace deck: you predict what kind of program it is (from the manifest), its domain nouns (from the module list), how it’s driven (the entry point), its spine (the central file), and finally the first paths worth tracing — each hop revealing the real lines. It’s written to a file (-o, default explore.md; --workspace places it inside a workspace), and you walk it from the web picker: run alix and pick it.

15 · The web app

alix is a web app: review, browse, and the exam all run here. alix opens a small local web server and shows you its URL, writing to the same progress store that alix stats/alix list read: what you grade here is exactly what they show. It’s especially handy on a tablet or phone, where touch (and images) work naturally.

alix                                   # the deck picker, at http://127.0.0.1:7777
alix --port 8080                       # a different port
alix --lan                             # reachable from other devices on your network
alix ~/decks-maria --lan --port 7781   # serve one folder as its own scoped root

Choosing decks in the browser

Run alix and the page opens the deck-selection screen. Up / down move between decks; a search box in the header filters the list (focus it with /). Focus a deck and Learn it with Enter (a facts deck opens a review, a trace opens a walk) one deck per session. Browse on b opens a read-only, in-page read-through instead: step the cards with Prev/Next, Esc to leave. Focusing any deck opens an inline focus drawer beneath it: it shows the deck’s preamble (the prose under its title, if any) and a per-card tier heatmap: neutral for an untouched card, grey for one merely seen (shown to you at least once), white once acquired (correct at least once), green/yellow/red for a learned card by how well you’d recall it right now, purple once retired. When the deck has a review order that heatmap splits into named regions you can pick to drill (click one or step through with ← / →); otherwise it is a single whole-deck bar. On a workspace row instead, → enters it, and Esc or Backspace backs out. After a session, Leave (on the summary) or Esc (also the footer’s Back chip while inside a drill-in) returns here, so you can switch decks without restarting. Every review starts from this screen; there’s no direct deck launch. A focused deck’s split Depth… button opens the depth menu (Scheduling) without starting it.

A workspace row that has a personal deadline set shows a small chip: a date, days left, and ready percent, colored to flag urgency inside the last week or past due; the same readout sits inline behind the title once you drill in. Press d (or the row’s Ready by… action) to set, move, or clear it from an inline date prompt.

Library actions

The picker’s ☰ menu carries five actions that used to be terminal-only: everything below is an /api/* endpoint, so it’s also on the wire for other clients (see docs/API.md):

  • Add deck…: one sheet, three ways in, all landing in a chosen destination (the library root or a workspace): generate a deck from a URL (with optional guidance) the same way alix generate does, but URL sources only, a local-file source stays CLI-only, since a LAN token holder must not be able to point the server’s AI at the server’s own filesystem; import an Anki .tsv or an alix .md file; or receive: paste a wormhole code, or upload a .zip.
  • Share…: sends the focused row (deck, folder, or workspace; the served root if nothing’s focused) device-to-device over a wormhole code, or download as .zip as the offline fallback. Personal state (progress, recent list, local pacing) stays home either way.
  • Remove from library…: permanently removes a focused loose deck, workspace member, or whole workspace and its Alix-owned progress, frozen assets, augmentations, and backup siblings. The sheet first lists the stakes, then requires the exact row name. Removing a workspace preserves ordinary source files and uninitialized Markdown, so its folder remains if either is present. A partial failure stays visible with completed and failed artifact labels plus the alix doctor recovery step. There is no undo.
  • Reset…: wipes a row’s progress. Gated on typing the row’s name back exactly, since this can’t be undone; needs a focused row.
  • Doctor: the free environment checks (config, store, decks, backend, share) as ✓/!/✗ rows, screenshot-able for handing to whoever set up the instance. The costed --backends probe stays CLI-only.
  • Pair a device: a QR of the pairing URL plus the URL itself, to scan from a phone or tablet. Needs --lan; a localhost-only instance shows a hint instead (nothing reachable to scan).

Augmenting a deck from the picker

Focus a deck and press a (or its Augment button) to open the Augment screen: the browser face of alix deck augment. Each of six targets, choices, notes, questions, key points, format, and order, gets its own card: a short, plain description of what that augmentation does, a small neutral before/after preview, its coverage count, and its action. Generate fills only the cards a target is still missing, run as a background model call while the page polls (a spinner shows it working); Remove clears a target, and the order card adds or drops named topologies. Each card has its own compact guidance input, feeding the same --with steer as the command line, with a kind-specific example as its placeholder so you can see what a steer is good for; a batch carries each ticked card’s own guidance. It writes the same augment/deck-<token>.json document review reads, so this only saves you the trip to the terminal.

Cached per-card augmentations are tied to the question and answer they were generated from. Editing either makes that card reappear as a gap, so its augmentations regenerate on the next augment run.

The action also works on a workspace or folder row: the same screen opens over all its decks at once, so a Generate fills a target’s gaps across every member, Remove clears it across every member, and an Order generated here is one workspace-wide pedagogical path. A workspace additionally gets an Icon card: Generate draws (or redraws) the small emblem shown on its picker row, steered by the card’s guidance.

Tick several targets and press Generate selected to run them in one batch (a Select all button at the top ticks everything that can run): it shows a rough estimate of how many generations that will take, then walks each ticked card through its own status, queued, generating, done, or failed, as the batch runs. A target failing doesn’t stop the others; a single per-target Generate still works the same way it always did.

On the Claude backend a batch shares one conversation: the first target sends the cards once and every later target refers back to them by index, which is cheaper and a little faster than re-sending the deck per target. Other backends, and single-target runs, keep making one self-contained call per target. A failed target starts a fresh conversation for the rest of the batch.

The format target is a non-destructive reshaping pass: for each plain card whose answer is poorly shaped (a list crammed into prose, a run-on sentence that wants to be lines) it caches a tidier front, split answer lines, an optional note, and a suggested reveal-method: applied at display time without touching the deck file or card identity. Both review and browse show the reshape, so the two views match. It’s an AI heuristic, so it can miss or produce an unhelpful reshape; Remove clears it with no lasting effect.

Every check, at every depth, plus the AI features

Every check works in the browser, at whichever session depth you picked: a flip or cloze reveal, a line reveal (it auto-scrolls to the newest line), a typing Reconstruct check (each line marked ✓/✗ with the correct answer shown, then you grade), an explain Reconstruct check, and the multiple-choice pick: a new card’s attempt-first on-ramp, or a genuine Recognize-session question (tap an option; a correct pick offers the quiet “I guessed” undo). A revealed note uses the same content-column width and text size as the answer or choices above it. Controls are big tap targets and follow your configured key bindings (the page reads them from the server). A dim “N left” count in the header shows how many cards the session still holds; it can tick up when a card you missed cools back in for its retry. The ☰ menu is context-aware: during review or a trace walk it holds Ask Tutor; on the deck picker, the library actions above plus keyboard shortcuts and about, with Theme… and Draw answers (a per-device toggle, see below) in both. The ⟳ button (also key r) re-reads your config, so a changed decks_dir takes effect without restarting (scoped alix <dir> instances stay pinned to their folder), and re-fetches workspace icon images, so a regenerated emblem shows without a reload.

The AI features come along too: the tutor, the AI exam, and trace walks all have a web surface, each running its model call on a background thread while the page polls, so the single-threaded server never blocks.

Draw input

A input: draw card, or a flip/explain card with the ☰ menu’s Draw answers toggle switched on, swaps the usual typed/reveal input for a small canvas: Pen · Eraser · Undo · Clear, then Reveal. The drawing stays on screen (frozen, not editable) while you self-grade against the card’s normal reveal, then it’s discarded; nothing you draw is saved or sent anywhere beyond rendering it in the browser. It’s honored on flip/explain cards only, and there’s no OCR or vision model reading it back: grading is on you, same as any other self-graded card.

Themes

The web UI ships a gallery of colour themes: the alix Dark/Light originals and a Kids group (Sunrise, Ocean, and Berry, the same three themes the kids app offers, so a kid moving up to the grown-up app can keep the look they grew attached to), plus crowd-favourite editor/slide palettes (GitHub, Dracula, Nord, Solarized, Gruvbox, Catppuccin, Tokyo Night, Monokai, One Dark, Ayu, Rosé Pine, Everforest). Open the Theme… popover from the ☰ menu (a small bar button on the trace walk): a grid grouped Light / Dark that previews on a sample card as you hover and re-themes the whole app when you click one, remembering your choice in the browser (kept in localStorage, not the config). The palette lives in a shared theme.css the server hosts, so every screen (review, browse, and trace walks) themes together.

Kids mode

alix can also serve a second, touch-first frontend aimed at kids (roughly age 10). Set audience = "kids" in [serve] (see Configuration) and point it at a folder an adult has already set up:

alix --config kids.toml ~/decks-family --lan --port 7781

A box is a workspace: the home screen shows the boxes as a grid, tap one to see its decks with a ⭐ mastery indicator per deck, tap a deck, then pick that deck’s depth: 👆 Tap the answer (Recognize) or 🗣️ Say it yourself (Recall); a caught-up choice disables itself instead of starting an empty session. Review works the same way underneath as the regular app (reveal, then the mascot says a short “why” instead of a bare note, then self-rate) with a 💬 Ask Alix button that opens a kid-safe tutor overlay scoped to the current card.

v1 is consumption only: it covers reviewing pre-made boxes at Recognize and Recall depth, plus the tutor. Augmenting a deck, the AI exam, and traces stay adult-only for now. An adult prepares a box in the regular web app, then hands the kid a kids.toml and the box to open. It’s the same engine and the same /api/* contract underneath, just a different page: self-hosted Baloo 2 type, warmer colours, and no keyboard required.

Building a client?

The JSON API the web app itself speaks is a documented, client-agnostic contract: docs/API.md in the repository (endpoints, DTO field tables, the flows, and the stability rules) with every response shape pinned by snapshot tests. Native or alternative clients build against that file.

Local by design

The server is deliberately local-only: no accounts, no database. By default it binds to 127.0.0.1 (this machine only). --lan binds all interfaces so another device on your network can reach it: at startup it prints the pairing URL with the machine’s real IP, plus a scannable QR code, right in the terminal. Serving with --lan auto-generates a pairing token and requires it on /api/*, so the network endpoint isn’t wide open; pin your own with --token or [serve] token. Open the printed …/?token=… URL (or scan the QR) and the page attaches the token for you. AI requests still run the model CLI on the host, so only use --lan on a network you trust. The default port lives in the [serve] config section; --port overrides it.

alix <dir> serves that folder as a self-contained scoped root: its own catalog, shareable augmentation and assets, plus private per-deck progress and recent history colocated by default. A workspace store setting can move the private user files without moving shareable material. Several instances run happily side by side, one per family member, say: alix ~/decks-maria --lan --port 7781.

If a launch misbehaves, alix doctor checks the setup (config, progress store, decks directory, backend CLI) and prints a one-line remedy per problem. The Doctor sheet also names this instance’s local log file.

Sending a log with a bug report

Every running server keeps a small local diagnostic history without requiring a flag. Open Doctor from the picker menu to see the exact path for the current profile. The current file is named alix-<profile>-<digest>.log; after it reaches 5 MiB, the previous part is alix-<profile>-<digest>.log.1. On a default Linux setup these live under ~/.local/state/alix/. Attach both files when the problem may have begun before the current file.

The log contains minted deck and card IDs and operational timings. It does not contain card text, notes, tutor or exam text, deck names or titles, or file paths. Alix never uploads it and never reads it back. Deleting either file does not change your decks or progress; the current file is recreated next time the server starts.

19 · Pairing a device

alix’s web server can lend a paired phone its AI backend for the tutor, the exam (including a trace’s compression exam), deck generation, and note-taking, over /api/remote/*: the phone keeps its own decks and progress, the desktop only computes answers.

The pairing token changes on every restart

alix --lan prints a fresh, random pairing token each time the server starts. This is the single biggest papercut in pairing a device: if an app that paired fine yesterday suddenly can’t reach the server, the token most likely changed on the last restart. Re-pair with the freshly printed URL, or pin one that never changes:

[serve]
token = "pick-your-own-fixed-token"

With token set, --lan reuses it instead of generating a new one, so a saved pairing survives restarts.

What the remote surface does

Nothing under /api/remote/* writes the server’s own progress store, session, decks, or recent list; it only computes an answer and hands it back. A tutor question re-sends the whole conversation with every call, since the server keeps no session for a remote client. An AI exam sitting is graded on the server, but the result, any remediation cards, and what counts as mastered stay the phone’s to keep. A deck generation call hands back the full deck text and a suggested file name; a note condense hands back up to three lines. Either way the phone decides where they land: the decks folder or the deck file, never the server.

The server side of this ships from 0.6.0; see docs/API.md, section 4.10, for the wire contract if you’re building against it.

Pairing the mobile app

On your computer, run alix --lan and note the URL it prints (the same one [serve] token can pin, above). On the mobile app:

  1. Open Settings (the ☰ button on the deck list) and tap Connected devices.
  2. Paste the printed URL into the sheet and tap Pair.

The app checks the server before saving anything, so a bad paste or an unreachable desktop never gets stored silently. It shows one inline line naming what went wrong:

  • an unparseable paste: that does not look like an alix pairing URL
  • a desktop it can’t reach: no alix answered at <host>:<port>
  • a desktop too old for this app’s remote surface: alix <version> found, this app needs 0.6.0 or newer
  • a desktop that answers but rejects the token (most often a server that restarted, and minted a fresh token, since the URL was printed): alix answered but refused this token. Copy a fresh pairing URL from the server.

On success the sheet closes with a note of which host you paired with. The same Connected devices row reopens the sheet later, now showing the current host:port and an Unpair button; unpairing only clears the saved config, nothing else on the phone changes.

What’s borrowed once paired

Once paired, review gains things it doesn’t have offline:

  • An Ask chip, shown once you’ve attempted the current card (revealed it, picked a choice, submitted a typed answer, or walked all its lines) but not before: the same attempt-first rule the web tutor follows. It opens the same question/answer flow as the desktop tutor, including Make a card and Make a note (condenses the exchange into up to three lines and appends them to the deck file on the phone, an empty result saying so rather than doing nothing silently), re-sending the whole exchange to the paired desktop on every turn (the server keeps no session of its own for a remote turn).
  • A Take the exam chip on the session summary, for any deck that declares a source:. It opens a full-screen exam: one question at a time, then a Pass/Partial/Fail breakdown per question and, on a fail, a Turn the gaps into cards button. A pass and any remediation cards it creates land in the phone’s own progress store, exactly like an offline grade, matching the rule above: the server computes, the phone keeps.

A trace deck reaches the exam differently: its walk (predict, reveal, self-grade) runs entirely on-device, no pairing needed. Only once paired does the walk’s done screen offer “Take the exam” for the trace’s compression question, graded on the desktop the same way a fact deck’s exam is; a fail is re-walked rather than turned into remediation cards, since a trace is a path, not a card pile.

The Settings page also gains a Generate deck row: give it a URL and optional guidance, the desktop generates the deck text the same way alix generate does, then the phone asks where to save it (the same folder browser the shared-decks setup uses) and writes it under a collision-free file name. It follows the same liveness rule as the two chips: the row appears only while the phone has confirmed the paired desktop is reachable and new enough, and is simply absent otherwise.

The Ask chip, the Take the exam chip, and the walk’s own exam offer all depend on the phone having confirmed the paired desktop is reachable and running at least version 0.6.0; there is no retry chrome for a dead or too-old server, the chip or offer simply is not there.

If the desktop answers but rejects the token partway through a review, an exam, a note, or a generation (the restart case above, caught mid-session instead of at pairing time), the phone shows one SnackBar: “Pairing expired. Pair again from Settings → Connected devices.” On the review and exam screens it carries a Re-pair action that reopens the pairing sheet directly; the tutor sheet’s own SnackBar sits under its own still-open modal and has no room for one, so there you follow the message’s own instruction instead. Pinning [serve] token is what stops this from happening in the first place.

Security posture

This is plain HTTP on your local network. The bearer token guards against someone stumbling onto the server by accident, not against a hostile network: anyone already on your LAN who gets hold of the token can use it. For anything beyond your own LAN, put alix behind a VPN or a reverse proxy; alix itself will not grow TLS or accounts.

18 · The mobile app

There is a native Android app: the same review loop as the web app, running the same core (parser, scheduler, progress store) compiled into the app, so it works entirely offline, including a trace deck’s predict/reveal/self-grade walk. It is early software with a deliberately small surface: reviewing decks. Pairing it with a running alix server on your network lends it the tutor, the AI exam (a trace’s compression exam included), deck generation, and note-taking: see Pairing a device.

Settings (the ☰ button) → Theme picks from the web gallery’s 18 non-kids themes (the three Kids palettes are web-only; see Themes); the app re-themes live, no restart.

Install

Grab alix-arm64-v8a.apk from the project’s GitHub Releases (the alix mobile vX.Y.Z releases) and install it. Android will warn about installing outside a store; that is expected for now. The app works on Android 7+ and ships a few sample decks so a fresh install has something to review.

Settings → About shows two versions: the app’s own and the embedded core’s. The app has its own release stream; it does not track the CLI’s version.

Your own decks: a shared folder

By default the app keeps decks in its private storage. To review the decks you actually maintain, point it at a real folder on the phone:

  1. Sync your decks folder to the phone with whatever you already use (Syncthing is the natural fit: local, no accounts).
  2. In the app: Settings → Decks folder, then Choose shared folder…. Android 11 or newer.
  3. The first time, Android opens its All files access page: alix reads and writes plain files in a folder another app manages, which is exactly what this permission grants. Enable it, go back, choose again.
  4. Pick the folder. The app lists it immediately; each initialized deck’s progress is written as progress/deck-<token>.json, exactly like the desktop, so it travels with the folder.

Use app storage in the same sheet switches back; nothing is deleted either way. If the folder becomes unavailable (permission revoked, folder gone), the app falls back to its private decks for that launch and says so; fixing the cause heals it on the next start.

Workspace deadlines

A workspace’s personal “ready by” date shows on its row (date, days left, and ready percent, colored to flag urgency inside the last week or past due) and again once you drill in, the same readout as the web picker. Long-press the workspace row to set, move, or clear it. The date lives in the workspace’s own alix.local.toml (see Workspaces), so a synced folder carries it between phone and desktop, and the phone’s own offline sessions bend their scheduling toward the date exactly as the desktop does.

One writer per deck

Progress is split into one versioned document per deck. A computer and phone can review different decks in the same synced folder without rewriting the same file. Alix does not merge concurrent histories for the same deck (deliberately: fail loud beats a silent merge that corrupts scheduling), so let sync settle before switching that deck to another device.

Two guards back the rule:

  • If another device wrote that deck’s progress minutes ago, the review screen says so before you grade anything.
  • If the folder contains a sync conflict file (Syncthing’s progress/deck-<token>.sync-conflict-….json), the deck list warns loudly. Stop both writers and sync, back up the folder, and deliberately keep the complete document you trust at progress/deck-<token>.json. Do not combine schedules by hand; there is no merge. alix doctor <folder> on the desktop lists every conflict and should be clean before you resume.

Two Syncthing tips: add *.json.tmp to the folder’s .stignore (alix writes through a temp file; there is no point syncing it), and prefer “send & receive” on both sides so the phone’s grades actually travel back.

Install the same Alix version on every device that writes a synchronized folder. For a pre-1.0 persisted-state format break, stop every writer, back up the folder, complete the release’s external conversion procedure, synchronize the resulting per-deck documents, and only then resume review.

16 · Configuration

alix works out of the box; the config file is for when you want to change key bindings, point at a different decks directory, or tune the AI features. It lives in the platform’s config directory (on Linux ~/.config/alix/config.toml); create it with alix config --init, and inspect the active key bindings with alix config.

Key bindings

All keybindings live under [keys], one subtable per surface: [keys.review] (the review screen), [keys.picker] (the deck picker), and [keys.browse] (the browse overlay). Every action takes a list of keys (the first is shown in the footer). To grade self-graded cards with j/k/l:

[keys.review]
failed = ["j"]
partly = ["k"]
passed = ["l"]

Keys are a single character ("j"), a special name ("space", "enter", "tab", "esc", "backspace"), or either with a ctrl- prefix ("ctrl-s"). The rebindable [keys.review] actions are failed, partly, passed, reveal, hint, submit, skip, remove (default ctrl-x), ask (default ?), promote (default ctrl-p), continue, restart (default r), quit, up/down (defaults k/j) to move within a multiple-choice or key-point list (the arrow keys always work too), and the tutor’s distill actions make_note (default ctrl-n) and make_card (default ctrl-d). While you’re typing an answer (a reconstruct check), plain-character bindings are ignored so they can’t shadow your input: use ctrl-/special keys for hint, skip, and quit there. Pass a different file with --config <path>.

The picker’s navigation is [keys.picker] (up, down, open, back, filter, mastered, plus depth to open the depth menu, recognize/recall/reconstruct to pick within it, and cram to toggle its tick-box, defaults v, 1/2/3, and c), the browse overlay has its own [keys.browse] bindings, and the web server reads its default port from [serve]:

[keys.browse]
next = ["l", "n", "space"]
prev = ["h", "p"]
remove = ["x"]
quit = ["q", "esc", "ctrl-c"]

[serve]
port = 7777
# token = "..."   # pairing token required on /api/*; --lan auto-generates one (printed, with a QR)
audience = "adult"   # or "kids", which frontend `/` serves, and the tutor's voice (see 15 · The web app)

[log]
max_bytes = 5242880   # cap for each of the current and rolled files
verbose = false       # also record verbose targets such as HTTP timings

(Jump-to-first/last stays fixed at g/G, and the arrow keys always move.)

The server log is always on. [log] max_bytes bounds each of its two files and must be positive. [log] verbose = true adds the HTTP timing target to the normal card-selection records. For live debugging, --log http,select also mirrors the named targets to stderr and enables verbose file records for that run. It does not add card content, deck names, or paths.

Review pacing

The [review] section tunes the FSRS scheduler shared by the Recall and Reconstruct depths:

[review]
retention = 0.9          # FSRS target recall probability (0.70–0.99); higher = shorter intervals
retire_after = "1y"      # a card rests once its Recall interval reaches this ("2w", "6m", "30d", or "never")
acquire_cooldown = "5m"  # settle gap before a new card's first quiz ("90s", "10m", "1h"; "0" = none)
max_session = 10         # cards a single sitting serves (default 10)
new_cards_percent = 30   # new-card share of max_session; the rest are due cards (default 30)

retention is the recall probability FSRS schedules for. retire_after is when a card retires (rests until alix reset); "never" keeps it in rotation forever. acquire_cooldown is the settle gap between seeing a new card and its first graded quiz, and the same floor keeps any just-seen card (a miss, a wrong pick) from returning immediately, so one knob paces both. A bare number is minutes; "0" disables the gap.

max_session is how many cards one sitting serves; new_cards_percent is the new-card slice of that cap (so at the defaults, three new and seven due out of ten). Whichever pool comes up short, the other fills the cap, so a fresh deck serves ten new and a deck with nothing new serves ten due; a big backlog just slows introductions proportionally. A workspace can override any of these keys for its own decks in an alix.local.toml (see Workspaces). The precedence for the cap is --session N on the launch > max_session > the built-in 10; new_cards_percent has no launch flag.

Ready by a deadline

Two more [review] keys exist only in a workspace’s alix.local.toml, never in the global config (which rejects both outright):

[review]
deadline = "2026-09-01"   # a personal "ready by" date; the day itself counts
deadline_ramp = "14d"     # how early the pre-deadline ramp starts ("2w"; "0" = cap only)

deadline is an ISO date (YYYY-MM-DD). deadline_ramp takes a bare number of days, "<n>d", or "<n>w"; "0" caps intervals at the days left without ramping retention early. Inside the window the target retention climbs linearly toward a fixed 0.95 by the deadline day (deliberately not a config key); see Scheduling for the full mechanics.

These keys are workspace-only: they take effect only in a directory with an alix.toml. In a plain decks folder, or on a loose deck, they parse but do nothing (no ramp, no picker readout, no doctor warning). alix workspace deadline refuses a non-workspace directory and points at alix workspace init.

The picker’s ready percent counts a deadline’s member decks as ready once mastered, or finished when they have no exam grounding. Mastery itself rests on the AI exam’s sampled questions, not a check of every card. Treat ready% as evidence toward readiness, not proof of it.

How deeply you drill is never configuration: it’s the session depth you pick per review (the picker’s Depth… menu). See Reveal & session depths. The old [review] depth config key (and the per-deck [review.deck."<file>"] override), a dial that fixed the drilling depth from config, is gone, not renamed; a config that still sets either now fails to load.

Backends

By default all AI calls go through the Claude Code CLI. You can switch to one of the other supported CLIs with backend in [ask]:

[ask]
backend = "claude"   # default, Claude Code CLI
# backend = "gemini"  # Google Gemini CLI
# backend = "codex"   # OpenAI Codex CLI
# backend = "copilot" # GitHub Copilot CLI

Auth is each CLI’s own login: alix stores no API keys. Install whichever CLI you want to use and run its login command once.

Each backend is granted read-only tools only (file reading; web fetch where the backend supports it). Codex runs under a network-blocking sandbox rather than a tool allowlist, so it can read local source files but can’t fetch URLs: a URL-based exam or alix generate will refuse and tell you to use a local file instead, or switch backends.

Run alix doctor --backends to send a quick test request to the configured backend and confirm it’s installed, signed in, and responding. --all-backends probes all four.

The multi-turn tutor works on every backend: Claude uses its native session flags (--session-id / --resume) for efficient continuation; other backends re-inline the accumulated Q&A transcript into each follow-up so the context carries over (the prompt grows with the conversation rather than being resumed efficiently).

The AI sections

Each AI feature has its own section, all reusing the [ask] command and permission settings:

  • [ask]: the tutor: command (how to invoke the CLI), backend, permission_mode, the tool allowlist, a model override, timeout_secs, an effort, source_access (local source grounding; a workspace manifest may override it, see the tutor), and preflight_threshold (warn and confirm before spending a large model call on a local source tree bigger than this many bytes; 0 proceeds silently).
  • [generate]: alix generate’s deck drafting: model, the absolute timeout_secs (3600), and idle_timeout_secs (300, or 0 to disable). The latter is a resetting inactivity limit for structured-event backends and a nonrenewing absolute fallback for unstructured backends. Other controls are max_cards (100, a soft ceiling: exceeding warns, never truncates), default language and audience, card_style (mixed, plain, cloze, or authored-choices), extra, a prompt override, and review. Per-run flags override the language, audience, and style defaults. The terminal shows calm live progress, but partial generated cards remain hidden until the result passes validation.
  • [exam]: the AI exam: model, timeout_secs (300), num_questions (5), pass_threshold (1.0), strictness (balanced), extra.
  • [trace]: alix generate’s trace and plan passes: model defaults to unset, which resolves to the backend’s strong model where it defines one (Claude: opus), and effort defaults to "high" (the build is correctness-critical and amortized); also timeout_secs. auto_grade (default false) has the model grade your typed predictions during a trace walk, a model call per hop, at the [ask] tier.
  • [ai]: alix deck augment’s generation targets: a model override, distractor_count (3), variant_count (4), keypoint_count (5), and timeout_secs (300, sized for a whole-deck batch).

Decks directory and storage

By default alix looks for decks in ~/decks; set decks_dir to change it. Shareable material and private user files default to that folder, one document per initialized deck:

<decks_dir>/
├── augment/deck-<token>.json
├── progress/deck-<token>.json
└── recent.json

progress/ is private, indispensable learning state: schedules, review history, exam state, virtual cards, and the last writer. augment/ is regenerable, shareable material: generated choices, notes, key points, variants, and topologies. It stays beside the deck so sharing the deck can carry its generated study material without carrying personal history. The stable deck id (deck-<token>), not the Markdown filename, selects both documents, so renaming a deck keeps their ownership stable.

Bare alix and alix <dir> use the same user-files root when <dir> is the configured decks_dir. A workspace, or any other folder served with `alix

`, keeps its shareable `augment/` and `assets/` beside its decks. The `stats`/`list`/`reset` commands take a deck, folder, or workspace and resolve the same private progress documents. `--store ` and a workspace's `store = "..."` manifest setting override only the user-files root for `progress/` and `recent.json`; they do not relocate augmentation or assets. Relative workspace `store` values are anchored to the workspace.

Each document carries its owner ID, format version, and revision. Saves write a sibling .json.tmp and atomically rename it into place. A process that can see that its loaded revision is stale refuses to overwrite the newer document. If the replacement commits but the final directory flush fails, Alix reports the failure while retaining the committed revision in memory, so a later save can retry instead of remaining stale forever. This protects local overlapping writers; it cannot turn disconnected folder synchronization into a transaction.

Alix is pre-1.0 and reads only the current version-1 per-deck documents. A persisted-state format break is handled before installing that build: back up the affected files, perform any one-time conversion outside production Alix, and verify the result with alix doctor <folder>. Production does not contain runtime compatibility branches or converters for superseded pre-1.0 layouts.

Backing up

Everything Alix stores is plain files in one folder, so a backup is a copy of that folder. Use whatever you already use for folders: a cloud drive, git, rsync, Time Machine, or cp -r. Alix manages no backup archives or generations of its own: a general backup could only reproduce what those tools already do, losslessly, over the same plain files. What it does keep is one .bak safety net per overwrite: alix deck restore swaps a deck (file, review history, augmentations) with the backups the last overwrite left behind. Keep an independent copy of any study history you care about.

What Alix does guarantee is that its own writes cannot corrupt your files. Every state, deck, and manifest write goes to a sibling temporary file that is flushed to disk, atomically renamed over the target, and (on Linux and macOS) has its directory entry flushed. An interrupted save leaves the previous file intact, never a half-written one; a kill-point fault-injection suite checks that at every filesystem operation, including partial multi-document saves and the deck/progress promotion boundary. Surviving a hard power loss additionally relies on flushing before the rename, which the code does but a test cannot simulate. That protects against Alix; your own folder backup protects against disk failure and accidental deletion.

Multi-device via your cloud drive

With the defaults, your decks, augmentation, assets, and progress live in one folder. Put that folder in a cloud drive you already use (Dropbox, iCloud, OneDrive, Syncthing) and it follows you across devices. Set store when you want progress and recent history to remain private to one device. Alix stays unaware that the folder is synced and uploads nothing itself.

For a free, no-account option that fits alix’s local-first grain, Syncthing works well: install it on each machine, pair the devices, and share your decks folder between them. It syncs the folder peer-to-peer over your own network, with no cloud company in the middle.

The writer boundary is now one deck, not the whole workspace. Different devices may review different decks in the same synchronized folder: their progress documents do not compete. For the same deck, use one active writer and let synchronization settle before switching devices. Alix does not merge concurrent same-deck reviews or decide which schedule is semantically correct. A disconnected collision therefore remains a Syncthing conflict copy. If a running web session’s own deck is replaced under it, that session can no longer save; the review screen shows a persistent banner and the fix is to reopen the deck (grades made after the collision are not kept).

Run alix doctor <folder> before recovery. For a progress conflict, stop both writers and synchronization, back up the folder, compare the canonical progress/deck-<token>.json with its deck-<token>.sync-conflict-….json copies, and deliberately keep the complete history you trust at the canonical path. Do not combine schedules by hand. Augmentation conflicts are regenerable: keep one complete augment/deck-<token>.json or move all conflicting copies aside and regenerate that deck’s augmentation. Resume synchronization and rerun doctor only after the canonical files are settled.

A card’s identity is a minted token alix writes into the file as an <!-- id: ... --> line, not a hash over its content. Editing any text, including the answer, preserves a card’s history; only deliberately replacing a card starts it over. (That’s the “editing is safe” rule from chapter 3, stated precisely.)

alix reset <target> clears progress so cards go “new” again: a whole deck, a folder or workspace (every member deck, plus a workspace’s mastered flags and virtual cards), a single card (--card <id-or-front>), or the entire store (--all); it confirms once unless you pass -y.

17 · Command reference

A quick index of the alix commands. Each links to the chapter that covers it in depth, where there is one. Run any command with --help for its full flags.

Reviewing

  • alix: serve the web app: the deck picker over your decks directory (~/decks), printing its URL.
  • alix <dir>: serve that folder as a self-contained scoped root: its own catalog and shareable augment/ and assets/, with private per-deck progress/ plus recent.json colocated by default. A workspace dir opens the picker drilled into it; its store setting may relocate the private user files without moving shareable material.

Every review starts from the picker. There’s no direct deck launch. Browsing a deck read-only, sitting the AI exam, and walking a trace are all reached from the web picker rather than as their own commands (see the web app).

The single-instance launcher’s flags: --lan / --port / --token (the web app), --session N (cards per sitting, overriding the [review] max_session config; unrelated to the AI backend’s own --session-id), --config <path>, and --log http,select (enable verbose file records and mirror the named targets to stderr). The session depth is picked in the picker’s split Depth… menu, an order or region in its focus drawer (scheduling), and the card order is the deck’s order: directive. How each card is checked comes from its reveal: combined with the session’s depth (reveal & session depths), not a flag.

Launch profiles

Launch profiles make it easy to run one named alix instance per person in a household, with its own decks folder, port, and adult or kids frontend. Each profile is a normal config file under the platform config directory’s profiles/ folder.

alix profile add timmy --decks ~/decks-timmy --port 7002 --kids
alix profile list
alix profile timmy
alix profile default timmy
alix --launch-all
alix profile remove timmy

alix profile <name> launches that profile on the LAN and reuses the stable token generated when it was added, so a phone can bookmark the printed URL. alix profile default shows the current default, names one when given a profile, and clears it with --clear; bare alix launches that default. alix --launch-all starts every profile in the foreground on its configured port. Ctrl-C or closing the terminal stops them together.

Progress

alix stats, alix list, and alix reset each take a deck file, a plain folder, or a workspace: a folder or workspace expands to its member decks, and each deck resolves to the user-files root the launcher would serve it with (--store > its workspace’s store > a served folder or configured decks root > the global store). Inside that boundary, progress is loaded from progress/deck-<token>.json; folder-wide commands aggregate the relevant documents in memory without creating an authoritative combined file.

  • alix stats <target>: progress overview, completion state, and a per-depth due count.
  • alix list <target>: every card with its Recall/Reconstruct schedule state, a ✓ once it’s recognized, and its due time.
  • alix reset <target>: clear progress (--card, --all; -y to skip the prompt). On a workspace it also clears the mastered flags and virtual cards in the workspace’s own store, after one confirmation.
  • alix reset --orphans [target] clears only orphaned progress: store keys that match no card or deck in the scanned decks (a stripped <!-- id: … --> comment, a hand-deleted deck, a double-mint). Orphans are never removed automatically (they are evidence), so this is the explicit opt-in. It scopes to a named folder/workspace store, else the decks-dir root store, and reads every progress document under it (the same documents alix doctor reports on). A single deck file scopes to that deck’s own document instead. A folder whose last deck was deleted is still a valid target. Every deck-like file in a folder is scanned for live ids, including one still awaiting its id: line, and any of them failing to parse aborts the sweep, since its cards cannot be told apart from orphans. Run alix doctor first to see what it would clear.

Deck dependencies (requires:) are edited by hand in the deck file. There’s no separate command for it.

The AI features

alix generate <source> is the one AI-authoring verb. --source-url <URL> records a public source (added to the deck or workspace source:) for later tutor and exam context after local evidence is frozen. What generate makes follows the source:

--goal <TEXT> scopes what every new deck or workspace teaches. --language <LANGUAGE> controls learner-facing output, and --audience <TEXT> controls assumed knowledge and difficulty. --card-style mixed|plain|cloze|authored-choices selects the facts-card shape; workspace trace items retain their checkpoint shape.

  • a web page URL or a local file → one facts deck (-o/--output, --cards, --review, --print, --force; --workspace <dir> writes it into that workspace).
  • a directory → explored first for an ordered learning plan scoped by the same controls: a one-item plan becomes a single deck, a bigger plan a workspace build, confirmed before it runs (--workspace <dir> sets the destination, --title/--icon name and brand it). --plan prints the plan and stops; --deck forces a single deck from a directory.
  • with --trace → a trace authored over the source, written as a trace deck (-o/--output, default explore.md; --workspace <dir> places it). --trace --plan prints a ranked menu of suggested traces instead.
  • an existing trace: stub deck → builds its checkpoints in place.

The rest of the AI-and-deck surface:

  • alix deck init <file>: explicitly initialize a hand-authored Markdown deck with stable deck and card IDs. Uninitialized .md files are ignored by discovery and never stamped merely because they contain ## headings.
  • alix deck augment <deck> --target <...>: precompute AI augmentations (choices, notes, questions, keypoints, format, order). The augmentation document stays beside the deck. --store affects only the private progress needed when the format target considers virtual cards.
  • alix deck copy <deck> <workspace>: copy one initialized workspace member, its owned frozen assets, and its augmentation into another workspace. Stable deck and card IDs are preserved; progress is not copied.
  • alix deck move <deck> <workspace> [--yes]: move the same public bundle, carry progress when the workspaces use different user roots, then remove the source. Refuses missing prerequisites and source dependents.
  • alix deck import <file.tsv>: import an Anki TSV export (no model CLI needed; --workspace <dir> imports into a workspace).
  • alix deck remove <deck> [--yes]: remove a deck and everything that is its alone: the file, its review history, its frozen assets, its augmentations, and any .bak backups. Total by design: nothing is backed up and it cannot be undone, which the confirmation states along with the stakes (cards with progress, reviewed-since date, the exact file list). A deck that others require: warns and names them; they unlock rather than break.
  • alix deck restore <deck>: swap a deck with its .bak backups (file, review history, augmentations), undoing the last overwrite (a forced import, a trace or workspace regeneration). Nothing is destroyed: the swapped-away state becomes the new backup, so running it again swaps back. There is nothing to restore after deck remove, which deletes the backups too.
  • alix workspace init <dir>: scaffold an empty workspace: an alix.toml (--title names it), an alix.local.toml (personal pacing: deadline, retention), and an empty decks/ plus assets/. Grow it with the --workspace flags above.
  • alix workspace update <dir>: reconcile frozen source-backed members with their recorded local sources. The first run stages an exact sibling workspace for review; --apply publishes it without another model call and --discard removes it. Changed or obsolete learning propositions retire their old card IDs; replacements receive fresh IDs.
  • alix workspace deadline <dir> [<date>|clear]: show, set, or clear a workspace’s personal “ready by” date (--config <path>); no argument prints the current one. Workspace-only, see Workspaces.
  • Tutor: the Ask button (or ?) in a session, Ctrl-N to save a note (the tutor).

The agentic generate runs measure the source size before running and prompt for confirmation when it’s large. Pass --yes to skip the prompts in non-interactive scripts. The AI exam runs unattended in the browser instead, so it can’t prompt: it truncates an oversized source and notes it.

Sharing

  • alix share <path>: send a deck file, a plain folder, or a workspace to someone over magic-wormhole (the wormhole binary must be installed, alix doctor checks). A folder is staged first so your personal state stays home: progress/, the recent list, alix.local.toml, temporary files, and conflict or backup files never travel. Matching augment/deck-<token>.json documents do travel, including when sharing one deck. A single frozen deck also carries its complete assets/deck-<token>/ directory. Tell the receiver the code wormhole prints. No wormhole around? --zip [--output <path>] writes the same staged copy as a .zip to mail or hand over instead.
  • alix receive <code-or-zip>: fetch what someone shared, by wormhole code or by a .zip path (the --zip fallback’s output, same landing either way). A deck lands in your decks directory (--workspace <dir> puts it inside a workspace; --force overwrites a same-named deck); a folder lands under its own name beside your other decks and is never overwritten. Personal files that leaked from the sender’s side are stripped on arrival.

Config & health

  • alix config: show the active key bindings; alix config --init writes the file.
  • alix doctor [dir-or-deck]: environment health checks, a one-line remedy per problem: the config parses, the current profile’s local log path is named, the progress store is readable, the decks dir scans, and the backend CLI is on your PATH. Name a deck file to lint it in depth (syntax, named-field at: locators, and frozen cards that have drifted from their live source). It withholds stale excerpts and reports a unique exact relocation, changed content, ambiguity, or a missing fingerprint. alix does not recognize or rewrite old deck formats; a deck written in one fails as ordinary invalid input (an unknown key, an id or locator that fails the current grammar). Over a folder or workspace it also reports identity problems across the decks as a set: duplicate deck or card tokens (naming which copy keeps the earned progress), store keys matching no live card or deck (orphans, clear them with alix reset --orphans), a non-canonical token, a frontmatter that can’t be stamped, an id marker away from its card’s closing line (the position stamping mints at), and cards still awaiting a token. For requires: it separates a dangling filename edge from a dangling deck-id edge, a card-… id pasted where a deck belongs, a file that only shares a required id’s name (the id wins, so add the .md extension to mean the file), and an un-prefixed token it suggests writing as deck-<token>. It nudges a source: that lists more than a few entries toward their common directory, and flags a source: pointing into assets/ (a deck keeps its real source, never its frozen excerpt fragments). It also names deck-like Markdown ignored until explicitly initialized, invalid or orphaned per-deck progress or augmentation documents, and synchronization conflict copies. Workspace checks also reject live source evidence, missing or cross-deck assets, local images outside the owning deck directory, and SHA-256 filenames that do not match their bytes. --backends additionally probes the configured AI backend end to end (one real, tiny request); --all-backends probes all four. --grading spot-checks the configured model’s exam grading against the hand-labeled calibration probes (a few real, costed calls, batched by strictness): answers that must not pass (wrong, empty, off-topic, incomplete at strict, flawed math derivations) and answers that should (correct ones, including full proofs). A failed must-not-pass probe is the serious direction (exam grades may be too lenient), while a missed should-pass probe only means the grader is harsher than intended. It’s a spot check, not a certification. Without an explicit repair flag, doctor is report-only and fixes nothing.
  • alix doctor [dir-or-deck] --repair-source-locators: after you review the reported citations, stamp fingerprints on currently addressed excerpts and apply unique exact locator rebases. Changed or multiply matching excerpts remain untouched and make the command fail. Deck and card IDs are preserved.
  • Folder and workspace runs also count accumulated .bak backup files (overwrite leftovers) with their total size, naming both remedies: alix deck restore <deck> swaps one back, alix doctor <dir> --remove-backup-files lists and deletes them all after one confirmation (--yes skips it). Backups warn, they never fail the run.
  • --config <path>: use a different config file.

How alix was made

alix is an AI-built project with a human maintainer. That description is more accurate than either “hand-written by a human” or “made autonomously by AI.” Models have produced a large share of the implementation, tests, documentation, and design drafts. The maintainer chooses the problems, sets the constraints, challenges the design, checks the result, and decides what enters the project.

This chapter follows the same rule: it was drafted with AI assistance for the maintainer to review. It does not pretend to be purely human-authored.

How a change happens

A typical change begins as a conversation, not as a model receiving the whole repository and independently deciding what to build.

  1. The human defines the job. The maintainer supplies the need, product boundary, and important constraints. For a substantial feature, that becomes a written specification and implementation plan before code changes.
  2. The coding agent investigates. It reads the repository, finds the relevant contracts and tests, proposes a design, and raises conflicts or missing decisions. It then edits code, tests, documentation, and examples together.
  3. Deterministic gates check the mechanics. Formatting, linting, unit tests, integration tests, contract snapshots, and end-to-end tests exercise behavior that can be checked repeatably.
  4. The human reviews the result. The maintainer reviews the product behavior, important design choices, the diff, and any visual result. They may reject the approach, narrow the scope, request another implementation, or approve a commit. The agent is not allowed to commit or push merely because its tests pass.
  5. Release checks examine the candidate. Prompt changes receive live-model calibration. Public documentation and screenshots receive a read-only semantic audit against the implementation. Release artifacts have their own platform checks.

The loop is deliberately conversational. The model contributes speed, breadth, and persistence; the human supplies intent, taste, risk tolerance, and the go/no-go decision.

What the models produce

During development, a coding model may write almost any repository artifact: Rust and Dart code, HTML and CSS, tests, specifications, plans, runbooks, changelog entries, and first drafts of prose like this chapter. It may also run the project’s tools and explain the evidence it used.

That development-time model is separate from the AI backends that alix calls as a product. The tutor, deck generator, trace generator, and examiner invoke a model CLI selected by the user. Replacing that runtime backend does not rewrite the application, and using alix for ordinary offline review does not require a model at all.

Generated output is not accepted because it sounds confident. Repository conventions push behavior into the shared Rust library, preserve stable card identities, require tests around important logic, and keep public contracts written down. Those constraints give both a human and another agent something concrete to inspect.

What the human reviews

The maintainer owns the decisions a test cannot make:

  • whether a feature belongs in alix at all;
  • whether the interaction stays calm and understandable;
  • whether a plan protects user data and stable file formats;
  • whether a screenshot or manual run actually feels right;
  • whether the explanation matches the intended product;
  • whether the remaining risk is acceptable.

This is not the same as independent review by a second engineer, and the project should not imply that every generated line has received a deep manual audit. AI-assisted development can produce changes faster than one person can study them. The response is to make review evidence durable: small commits, explicit plans, focused tests, source-linked decision decks, and release audits. These improve traceability; they do not turn a single maintainer into two reviewers.

What tests prove, and what they do not

alix separates deterministic software correctness from model-behavior quality.

The blocking make check gate uses ordinary Rust tests and a fake model CLI. It can prove that known inputs produce expected state transitions, errors are handled, contracts remain compatible, and AI plumbing behaves correctly for canned responses. CI can repeat those claims without network access or model variance.

Live-model calibration asks a different question: do current prompts produce useful, appropriately strict results? It is costed and non-deterministic, so it is run deliberately for prompt changes rather than pretending to be an ordinary unit test. The documentation audit is also a deliberate model call: it compares public text and images with current implementation evidence before a release.

Neither layer proves that every future model response will be good. A green suite also cannot prove that the product decision was wise, the architecture will remain maintainable, a migration is operationally safe, or a human understands every changed line. Manual review, calibration, release practice, and feedback from real use remain necessary.

Attribution and responsibility

The repository history preserves AI assistance with Co-Authored-By trailers. That is attribution, not a transfer of responsibility. A model cannot own a release, respond to a data-loss incident, or decide what risk another person should accept. The human who approves and publishes a change remains responsible for it.

The useful standard is therefore not “no AI touched this.” It is: the role of AI is disclosed, important decisions are inspectable, repeatable claims have repeatable tests, uncertain model behavior is evaluated as such, and a human makes the final call.