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

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 tracing 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 traces, and the thing that marks the deck a trace) and a exam: (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
exam: .
cite:
  - book: src
---

## 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: book:ch04-01-what-is-ownership.md:290-297 fingerprint: xxh64-0123456789abcdef -->
> [!NOTE]
> 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: book:ch04-01-what-is-ownership.md:322-343 fingerprint: xxh64-123456789abcdef0 -->
> [!NOTE]
> 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 key:file:start-end under one of the deck’s cite: roots (the keyed form is described with source citations; a range-less path cites the whole file, a bare key the frozen passage of a URL root), never comma-separated, since a stitched excerpt makes disjoint code look adjacent. Its fingerprint: xxh64-... field fingerprints the displayed lines. A live trace 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 exam:, then name the stub deck as alix deck generate’s source:

alix deck 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. The locators use the cite: key src, which the build declares for its input: the exam: directory, or the folder holding an exam: file (so a file is cited as src:<file name>:<lines>). When a cite: root the deck already sees holds the input, the build reuses that key instead; when none does and src is already taken, it stops before calling the model. A URL trace build works only in a workspace member deck: the URL becomes the cite: root src, and the model copies each checkpoint’s passage verbatim between a PASSAGE <token> line and an END PASSAGE <token> line, with a token alix mints for that build. Alix freezes each passage as a sha256-<hex>.txt object in the deck’s asset folder and cites it as <!-- at: src fingerprint: ... asset: ... -->. A missing or altered marker, or a checkpoint without a passage, fails the build naming the checkpoint, and nothing is written. 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.

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

alix deck 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 exam: 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 deck 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.

Tracing it

Pick the trace in the web picker, or on the mobile app (the trace runs fully offline there too): a trace deck opens as a trace: 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. Reveal — alix 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). The trace is self-judged and offline on every client. 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 trace drill is complete. There is no exam or separate capstone. The checkpoints keep their ordinary SRS state, and the trace reaches Finished when they graduate. A dependent deck that names the trace in requires: waits for that state.

Immediate freezing

Because <!-- at: key: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 exam: and cite: roots pointed at the real material (a path or a URL): they are never rewritten to point into assets/. Each <!-- at: --> keeps its key, path, and lines, keeps its excerpt fingerprint:, and gains an asset: field naming the content-addressed object:

<!-- at: code: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: key and path retain provenance for drift reporting, judged against that key’s own root, and a future deliberate source update. When that root is available and source_access permits it, the tutor browses it for surrounding context and staleness detection; offline, it reports 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 exam: — 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 with --warn-on-source-locator-drift it also warns about live-source drift.

A trace deck degrades gracefully — even when it is not being traced 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 traces offline.