Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ When requirements conflict, follow `docs/PRD.md`, then document the conflict and

When acting as a learner's tutor:

- Treat lesson Markdown as the canonical explanation. Direct the learner to its definitions,
worked example, and recap before supplementing it.
- Check only the prerequisite vocabulary needed for the active checkpoint. Explain a missing term
briefly, then return to the lesson; do not make chat the only source of a core concept.
- Ask the learner to predict before revealing outcomes.
- Direct them to manipulate the explorable.
- Give the smallest useful hint first.
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ All notable changes to `explorables` are documented here.
### Changed

- Reframed the local collection home as a general, status-first course library: available courses are primary, planned AI courses sit in a quieter roadmap, and visible product branding now uses `Explorables` while technical identifiers remain lowercase.
- `AI from First Principles` is versioned as `0.3.0-guided.1`.
- `AI from First Principles` is versioned as `0.4.0-foundations.1`, with self-contained canonical
lesson prose, prerequisite bridges, worked examples, implementation connections, failure modes,
and explanation-based recaps across all thirteen lessons.
- The product requirements, architecture, authoring guide, implementation plan, status, and model-learning roadmap now include guided delivery.
- The local course server now treats its default port as strict. An occupied port fails with an actionable message instead of silently changing the browser-storage origin.
- First-party and scaffolded host adapters now use browser course state as the progress authority and distinguish pausing from finishing or resetting.
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

**See how it works. Build it yourself.**

`explorables` is an Agent Plugins v1-compatible open course format and local runtime for technical learning through plain Markdown, sandboxed TypeScript interactions, real exercises, and a coding-agent tutor. A course is a normal folder. It needs no account, database, analytics, hosted executor, or LMS.
`explorables` is an Agent Plugins v1-compatible open course format and local runtime for technical
learning through explanatory Markdown, sandboxed TypeScript interactions, real exercises, and a
coding-agent tutor. The lesson prose is the canonical source for definitions and mechanisms; the
tutor adapts and reinforces it. A course is a normal folder. It needs no account, database,
analytics, hosted executor, or LMS.

The reference course is [AI from First Principles](examples/ai-from-first-principles). Its current thirteen-lesson foundation builds from gradients and linear layers through a trained tiny Transformer, cached autoregressive generation, sampling, and claim-aligned evaluation.

Expand Down Expand Up @@ -87,7 +91,7 @@ A course is a self-contained Agent Plugin with root `plugin.json`, a portable `s
## Principles

- The repository is the course.
- Course prose remains readable as plain Markdown.
- Course prose teaches the durable concepts and remains readable as plain Markdown without a tutor.
- Course code never runs in the main document context.
- Agents tutor through prediction, manipulation, debugging, and explanation.
- Learners deliberately run exercises; opening a lesson never executes them.
Expand Down
49 changes: 47 additions & 2 deletions docs/course-authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,36 @@ Resume is scoped to the course ID/version, browser profile, and web origin. Keep

## 4. Write a lesson

Lessons should remain useful on GitHub. A strong sequence is encounter, predict, manipulate, inspect, explain, implement, debug, and transfer. Those are headings and prose, not additional directives.
Lessons should remain useful on GitHub. The lesson Markdown is the canonical teaching material, not
an outline that depends on a coding agent to supply the missing lecture. A learner should be able to
identify the important terms, follow a concrete example, and understand the explorable's result by
reading the lesson without opening chat. The tutor then diagnoses missing prerequisites, asks
questions, gives hints, and helps the learner connect that material to code and tests.

A strong sequence is encounter, predict, manipulate, inspect, explain, implement, debug, and
transfer. “Encounter before explaining” means delaying the full mechanism until the learner has a
phenomenon to explain; it does not mean asking them to predict with undefined vocabulary or
notation. These stages are headings and prose, not additional directives.

Before the prediction, give the learner:

- a reason the concept exists;
- the prerequisite bridge from the prior lesson;
- plain-language definitions for every new term or symbol needed to make the prediction; and
- a small, concrete setup whose values can be inspected by hand.

After the interaction, include:

- an explanation that connects the observed values to the formal mechanism;
- at least one worked example with intermediate steps;
- a bridge from the representation to the relevant data shapes or code responsibility;
- the deliberate failure and why it violates the intended invariant; and
- a concise recap or self-check that requires explanation, not recognition alone.

There is no universal minimum word count. Reviewers should judge whether the stated audience and
prerequisites are enough to follow the lesson without an unstated chat-only explanation. Automated
validation should continue to check structure, paths, fallbacks, and execution; instructional depth
requires editorial review and learner playtesting.

```md
---
Expand All @@ -140,16 +169,32 @@ objectives:

# Queues

> Before running it, predict when the queue begins to grow.
A **queue** stores work that has arrived but has not finished. The **arrival rate** is how many new
items enter per second; the **service rate** is how many items leave per second. If arrivals add 5
items per second while service removes 3, the queue grows by `5 - 3 = 2` items each second until it
reaches a limit.

> **Predict:** With an arrival rate of 5 and a service rate of 3, what happens to a queue that starts
> with 4 items after two seconds? Record the intermediate queue length before running it.

:::explorable{src="../explorables/queue/index.ts" height="440" title="Queue simulator"}
Requests arrive on the left and leave at the configured service rate. When
arrival exceeds service, queue length increases until capacity is reached.
:::

## Explain the result

Each simulated step applies `next = current + arrivals - serviced`, then enforces the queue's lower
and upper bounds. The growing queue is therefore evidence of a rate imbalance, not evidence that
the service has stopped entirely.

:::exercise{path="../exercises/bounded-queue" command="pnpm test" title="Bound the queue"}
Implement the capacity check and run the supplied tests.
:::

## Check your understanding

Why can a queue grow even while the service is successfully completing work?
```

Only `explorable` and `exercise` are supported. Unknown directives fail validation. Relative Markdown links and assets resolve from the lesson file; paths may not escape the course root. Raw HTML is sanitised.
Expand Down
3 changes: 2 additions & 1 deletion docs/implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Updated: 19 August 2026
- Implemented the second v0.2 foundation increment: embedding lookup, RoPE-style positional rotation, causal multi-head attention, a pre-norm residual/SwiGLU block, next-token target alignment, four interactive lessons, and four protected exercises.
- Implemented the final v0.2 foundation increment: prefill and autoregressive decoding, equivalent cached and uncached attention, explicit cache memory and work accounting, and a deterministic tiny Transformer capstone with gradient checks, decreasing loss, generation, intermediate traces, and testable masking, shape, residual, and evaluation failures.
- Completed all thirteen `AI from First Principles` v0.2 lessons and connected the inference result to decoding policy and claim-aligned evaluation.
- Expanded all thirteen lessons into the self-contained `AI from First Principles` `0.4.0-foundations.1` course: lesson Markdown now carries the canonical definitions, notation, worked examples, implementation bridges, deliberate failures, and explanation-based recaps that the tutor adapts and reinforces.
- Implemented reusable opt-in Guided Course Mode with ordered learner and explorable-event checkpoints, locked future navigation, deep-link recovery, explicit skipping, confirmed Explore mode, reset, a question parking lot, and versioned browser-only resume state.
- Applied four ordered checkpoints and focus-aware tutor policy to all thirteen `AI from First Principles` lessons without changing sandbox permissions or automatic exercise execution.
- Kept courses without guidance backward-compatible and added schema, reducer, validator, browser, accessibility, persistence, and recovery coverage.
Expand All @@ -47,7 +48,7 @@ Updated: 19 August 2026

The v0.1 runtime MVP remains verified. Course-session continuity now provides a framework-owned resume surface, lesson-level local state for persistent courses, Guided checkpoint resume and confirmed rollback, page-exit flushing, storage-failure messaging, host-neutral state attributes, shared lifecycle language, and a stable strict development origin. Model Atlas implementation, local hardening, and clean-checkout verification are complete; pull-request CI remains.

`AI from First Principles` `0.3.0-guided.1`, Guided Course Mode, Agent Plugins v1 packaging, and the local course-library milestone are implemented. The course UI redesign and its responsive/theme follow-up are complete. The library presents the shared frontier core and DeepSeek, Kimi, Qwen, MiniMax, and GLM specializations as planned rather than runnable. Its DeepSeek and GLM cards now reflect the V4 and 5.2 endpoints. The next implementation increment remains the shared-core source freeze and its five research/comparison lessons, followed by the pinned model-specific courses. External DNS and learner-study evidence also remain.
`AI from First Principles` `0.4.0-foundations.1`, Guided Course Mode, Agent Plugins v1 packaging, and the local course-library milestone are implemented. The course UI redesign and its responsive/theme follow-up are complete. The library presents the shared frontier core and DeepSeek, Kimi, Qwen, MiniMax, and GLM specializations as planned rather than runnable. Its DeepSeek and GLM cards now reflect the V4 and 5.2 endpoints. The next implementation increment remains the shared-core source freeze and its five research/comparison lessons, followed by the pinned model-specific courses. External DNS and learner-study evidence also remain.

## Decisions

Expand Down
7 changes: 7 additions & 0 deletions examples/ai-from-first-principles/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ Run `pnpm course`, open the printed local URL, read `COURSE.md`, and introduce t

## Teach

- Treat the current lesson Markdown as the canonical explanation. Direct the learner to the
relevant definition, worked example, or recap before adding an explanation of your own.
- At the start of a checkpoint, check that the learner understands only the vocabulary and
notation needed for that checkpoint. Supply a short prerequisite explanation when needed, then
return to the lesson.
- Ask the learner to predict before revealing outcomes.
- Direct them to manipulate the current explorable.
- Give the smallest useful hint first.
- Adapt and reinforce the written course; do not make chat the only place where a core definition
or concept is taught.
- Do not implement central files under `exercises/**/starter/` before an attempt.
- Never read, modify, quote, or reveal `exercises/**/solution/`.
- Run the exercise's documented test command and explain failures.
Expand Down
11 changes: 7 additions & 4 deletions examples/ai-from-first-principles/COURSE.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
---
id: ai-from-first-principles
title: AI from First Principles
version: 0.3.0-guided.1
summary: See, implement, and debug the foundations behind modern language models.
version: 0.4.0-foundations.1
summary: Learn, inspect, implement, and debug the foundations behind modern language models.
license: CC-BY-4.0
audience:
- software developers
- computer science graduates
prerequisites:
- basic TypeScript
- algebra and arrays
estimatedHours: 15
estimatedHours: 18
repository: https://github.com/Doppp/explorables
language: en
tags:
Expand All @@ -25,7 +25,10 @@ guidance:

# AI from First Principles

Use the browser to form an intuition, then make that intuition survive code and tests. The coding agent is your tutor and debugger, not your substitute.
The lesson prose teaches the durable definitions, notation, and mechanisms. Use the browser to
form an intuition, then make that intuition survive experiments, code, and tests. The coding agent
adapts the explanation and helps you debug; it is your tutor, not the source of a hidden parallel
course and not your substitute.

## Lessons

Expand Down
9 changes: 7 additions & 2 deletions examples/ai-from-first-principles/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# AI from First Principles

An expanding interactive course for software developers who use AI tools and want to understand the machinery underneath them. The complete thirteen-lesson foundation builds from scalar gradients through a trained tiny Transformer, cached autoregressive generation, sampling, and claim-aligned evaluation.
An expanding interactive course for software developers who use AI tools and want to understand
the machinery underneath them. The lesson prose establishes the definitions and worked examples;
the explorables, exercises, and coding-agent tutor turn them into predictions, experiments, code,
and explanations. The complete thirteen-lesson foundation builds from scalar gradients through a
trained tiny Transformer, cached autoregressive generation, sampling, and claim-aligned evaluation.

## Prerequisites

Basic TypeScript, algebra, arrays, a terminal, Git, and the ability to read a test failure. The current course takes roughly fifteen hours.
Basic TypeScript, algebra, arrays, a terminal, Git, and the ability to read a test failure. No
machine-learning background is assumed. The current course takes roughly eighteen hours.

## Start

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ describe("stable loss and momentum", () => {
expect(crossEntropy([2, 1, -1], 1)).toBeCloseTo(crossEntropy([1002, 1001, 999], 1));
});

it("keeps loss finite when the target probability underflows", () => {
expect(crossEntropy([0, -1000], 1)).toBeCloseTo(1000);
});

it("clips the global norm without changing direction", () => {
expect(clipByGlobalNorm([3, 4], 2.5)).toEqual([1.5, 2]);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ExplorableModule } from "@explorables/explorable";
import { element, styles } from "../shared.ts";
import { gradient, loss, step } from "./model.ts";
import { classifyLossTrend, gradient, loss, step } from "./model.ts";

const module: ExplorableModule = {
mount(root, context) {
Expand Down Expand Up @@ -59,7 +59,7 @@ const module: ExplorableModule = {
history.push(parameter);
}
const finalLoss = loss(parameter);
const behavior = finalLoss < loss(-4) ? "converging" : "diverging";
const behavior = classifyLossTrend(loss(-4), finalLoss);
render();
context.recordExperiment({
label: `rate ${learningRate.toFixed(2)}`,
Expand All @@ -69,7 +69,7 @@ const module: ExplorableModule = {
finalLoss: Number(finalLoss.toFixed(3)),
behavior,
},
summary: `After four steps the run is ${behavior}.`,
summary: `After four steps the run shows ${behavior}.`,
});
};
const onReset = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { gradient, loss, step } from "./model.ts";
import { classifyLossTrend, gradient, loss, step } from "./model.ts";

describe("gradient descent model", () => {
it("has its minimum at three", () => {
Expand All @@ -10,4 +10,9 @@ describe("gradient descent model", () => {
expect(Math.abs(step(-4, 0.2) - 3)).toBeLessThan(7);
expect(Math.abs(step(-4, 1.1) - 3)).toBeGreaterThan(7);
});
it("distinguishes convergence, constant-distance oscillation, and divergence", () => {
expect(classifyLossTrend(49, 10)).toBe("converging");
expect(classifyLossTrend(49, 49)).toBe("constant-distance oscillation");
expect(classifyLossTrend(49, 70.56)).toBe("diverging");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@ export function step(parameter: number, learningRate: number): number {
}
return parameter - learningRate * gradient(parameter);
}

export function classifyLossTrend(
initialLoss: number,
finalLoss: number,
): "converging" | "constant-distance oscillation" | "diverging" {
const tolerance = Number.EPSILON * Math.max(1, initialLoss, finalLoss) * 16;
if (finalLoss < initialLoss - tolerance) return "converging";
if (finalLoss > initialLoss + tolerance) return "diverging";
return "constant-distance oscillation";
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { describe, expect, it } from "vitest";
import {
compareModelDescriptors,
parseModelAtlasDescriptor,
} from "@explorables/model-atlas";
import { describe, expect, it } from "vitest";
import { forward, initialTinyTransformer } from "../tiny-transformer/model.ts";
import gpt2 from "./gpt-2-small.json";
import { createTinyAtlasTrace } from "./model.ts";
Expand All @@ -15,6 +15,11 @@ describe("tiny Transformer atlas trace", () => {
const atlas = createTinyAtlasTrace(model, tokenIds);
const final = forward(model, tokenIds).tokens.at(-1);
expect(final).toBeDefined();
if (!final) throw new Error("expected a final token trace");
const embedding = model.embeddings[final.token] ?? [];
const residual = embedding.map(
(value, index) => value + (final.attentionOutput[index] ?? 0),
);

expect(atlas.steps.map((step) => step.stageId)).toEqual([
"tokens",
Expand All @@ -24,12 +29,18 @@ describe("tiny Transformer atlas trace", () => {
"normalisation",
"lm-head",
]);
expect(atlas.steps[2]?.values).toEqual([
final?.attentionWeights,
final?.attentionOutput,
]);
expect(atlas.steps[4]?.values).toEqual([final?.hidden]);
expect(atlas.steps[5]?.values).toEqual([final?.logits]);
expect(atlas.steps[2]).toMatchObject({
values: [final.attentionWeights],
rowLabels: ["weights"],
columnLabels: ["position 0", "position 1", "position 2"],
});
expect(atlas.steps[3]).toMatchObject({
values: [final.attentionOutput, residual],
rowLabels: ["attention output", "embedding + attention output"],
columnLabels: ["d0", "d1", "d2"],
});
expect(atlas.steps[4]?.values).toEqual([final.hidden]);
expect(atlas.steps[5]?.values).toEqual([final.logits]);
});

it("compares the executable model with the pinned GPT-2 configuration", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,16 @@ export function createTinyAtlasTrace(
vectorStep("embedding", embedding),
{
stageId: "attention",
values: [final.attentionWeights, final.attentionOutput],
rowLabels: ["weights", "weighted value"],
values: [final.attentionWeights],
rowLabels: ["weights"],
columnLabels: final.attentionWeights.map((_, index) => `position ${index}`),
},
vectorStep("residual", residual),
{
stageId: "residual",
values: [final.attentionOutput, residual],
rowLabels: ["attention output", "embedding + attention output"],
columnLabels: residual.map((_, index) => `d${index}`),
},
vectorStep("normalisation", final.hidden),
vectorStep("lm-head", final.logits),
],
Expand Down
Loading