Skip to content

Repository files navigation

Build an AI Agent From Scratch

Twenty-nine chapters and two capstones. One while loop that becomes Claude Code.

Read the diagram → break the simulator → run the file → take the quiz.

CI Deploy License: MIT Node 22.6+ Dependencies: none API key required: none Languages: EN · 中文

Read it →  ·  Star on GitHub  ·  Follow on X


Everyone can call a model. Almost nobody can explain why their agent works in the demo and dies on Tuesday, or why the twelve-step task costs forty times what they estimated. Closing that gap is what this course is for.

It starts with a while loop that calls a model. It ends with a context manager, a durable event log, a sandbox, a patch engine, an approval layer, a message-passing runtime, an eval harness and two complete agents. Each of those arrives only after you have felt the specific pain that made it necessary.

// Chapter 4. This is the entire agent.
while (true) {
  const reply = await model(messages, tools);   // the model decides
  messages.push(reply);
  if (reply.stop) return reply.text;            // it decided to stop
  for (const call of reply.toolCalls) {         // it decided to act
    messages.push(await runTool(tools, call));  // the world answers back
  }
}

Twenty chapters later that loop is unrecognisable, and you will be able to point at any line of LangGraph, AutoGen or the Claude Agent SDK and say what it is for.

No framework. No API key. No vector database. 6,191 lines of dependency-free TypeScript that run on a laptop in a few seconds.


Who this is for

You used a framework and it felt like magic. Build the loop once and StateGraph and RoutedAgent stop being vocabulary. C04 is the whole idea in 120 lines.
Your agent works in the demo and not on Tuesday. C13 is the failure taxonomy, retries, loop detection and budget enforcement — the four things that separate a demo from a system.
You have to sign off on shipping one. C24 is prompt injection, the lethal trifecta, least privilege, egress control and an approval design that actually holds.
You learn by breaking things. Twenty-nine simulators. Starve the context budget and watch the agent forget its goal.

Prerequisites: TypeScript or JavaScript, and having called an LLM API once. No machine learning background — nothing here trains a model.


What makes it different

📊 Diagrams of the mechanism Where tokens, latency, money and trust enter the path — not boxes and logos.
🎛 Simulators you can break Twenty-nine real implementations running in the page. Overflow the context window, corrupt a tool result, push a tool registry to 50 and watch selection collapse.
✅ 174 quiz questions Six graded per chapter, each with an explanation naming the section to reread.
🟦 Code that runs 31 self-contained TypeScript files. All 31 execute in CI on every push, offline, with a deterministic mock model.
📌 Outputs that are real Every "run it" block is generated by executing that chapter's file. CI fails if one drifts.
🌏 English and 中文 C00–C04 translated; the rest falls back to English with an honest banner.

The curriculum

LayerChaptersYou learn
The Shape of the Thing C00 Agent vs chain vs pipeline · the agency dial · blast radius · why the p99 is the number that matters
The Model C01–C04 The model call · structured output & the repair ladder · tools & dispatch · the agent loop
Context & Memory C05, C06, C07, C08, C09 Context engineering · retrieval as a tool · multimodal observations · memory & contradiction · durable state and resume
Reasoning & Control C10–C13 Planning · verification & the reflection ladder · composition primitives · failure & recovery
The Environment C14, C15, C16, C17, C18, C19 Code execution & sandboxing · the action space (code actions vs tool calls) · files, shell and apply_patch · MCP · skills (progressive disclosure) · human approval
Systems & Production C20–C24, C25, C26 Multi-agent topology · the actor runtime · evals · tracing · security · the interactive loop (steering, interrupts) · the server
Capstone I C27 A deep-research agent — plans, searches, verifies claims against evidence spans, cites, surfaces conflicts
Capstone II C28 A coding agent — orients in a repo, patches, runs the tests, reads the failure, repairs

Each layer answers a question the previous one created:

  1. An agent is a loop with a decision in it. …which tells you what an agent is. Now build the smallest one that works. So:
  2. The model, typed and tooled. …which leaves you with an agent that works and forgets everything. So:
  3. Context and memory. …which gives the agent a past. It still has no plan. So:
  4. Reasoning and control. …which makes it reliable inside its own head. Now let it touch the world. So:
  5. The environment. …which is one capable agent. Production needs more than one, and needs proof. So:
  6. Systems and production. …which is everything the course has to teach. Now assemble it twice.

Read them in order the first time. The dependencies are real.


Quickstart

git clone https://github.com/xinbetween/learn-ai-agent-from-scratch
cd learn-ai-agent-from-scratch

# --- the code (no dependencies, no key) ---
node --experimental-strip-types code/c04_agent_loop.ts   # start here
npm run check:code                                       # all 31 files

# --- the site ---
npm install            # only for `npm run check` — the site itself has no deps
npm run dev            # http://localhost:4321

Node 22.6+ runs TypeScript directly. On 22.18+ and Node 24 you can drop the flag:

node code/c04_agent_loop.ts
npm run agent code/c04_agent_loop.ts    # either version

Every file ships a deterministic mock model, so the whole course runs offline with no API key and no spend. C01 also includes an opt-in live-client demonstration that reads ANTHROPIC_API_KEY or OPENAI_API_KEY; the chapter programs remain deterministic.

Node's type stripping only erases types — it does not transform — so enum, namespace, decorators and parameter properties are unsupported. Every file avoids them, and npm run check:code proves it by executing all 31.


The outputs on the site are real

Each chapter ends with a "run it" block. Those are not illustrative: they are generated by running that chapter's file and pasting what it printed.

npm run sync              # re-run every code file, rewrite the output blocks
npm run sync -- --check   # fail if any block has drifted (this runs in CI)

npm run verify is the whole contract in one command — execute all 27 files, rebuild the site, and assert no output block has drifted:

27/27 runnable files execute cleanly.
built 29 chapters + 9 pages + 23 runnable files × 2 locales → dist/  (zh: 5/29 chapters translated)

The build refuses to ship an incomplete chapter: fewer than four sections, not exactly six quiz questions, a quiz answer index out of range, or a translation whose shape has drifted from the chapter it mirrors are all build failures, not review comments.


Repo layout

code/                      ALL chapter code lives here
  c00..c07_*.ts            one runnable file per chapter
  c27_research/            Capstone I — the deep-research agent
  c28_coder/               Capstone II — the coding agent
content/
  chapters/c00..c28.ts     chapter prose, exercises, Q&A, projects, quizzes
  pages/                   map, glossary, answers, Q&A, projects, setup, timeline
  zh/                      Chinese translations — see below
src/
  build.ts                 the generator: writes dist/ for every locale
  render.ts                shell, nav, sidebar, rail, chapter and page renderers
  landing.ts               the home page
  i18n.ts                  locales, URL shapes, UI strings, landing copy
  curriculum.ts            layers and site metadata
  ui.ts                    code(), fig(), lab(), note(), table() — the HTML helpers
  types.ts                 the content model everything is typed against
static/                    styles.css, app.js, favicon.svg
scripts/                   output sync, code check, dev server

Chapter ids are URLs, so C15–C07 were appended rather than inserted, and content/chapters/index.ts places them in reading order. Ids therefore run out of order in the sidebar gutter, on purpose: renumbering would have moved eleven chapters and broken every link anyone had shared.

The site is a static generator in about 1,500 lines with no dependencies. It builds to a folder of HTML you can host anywhere.

Every push to main builds and publishes to GitHub Pages via .github/workflows/deploy.yml; no build output is committed. Two environment variables are the whole deployment configuration:

Variable What it does
SITE_URL Absolute origin baked into the sitemap, robots.txt and canonical URLs.
SITE_BASE_PATH The sub-path Pages serves from — /<repo> for project hosting, empty for a custom domain at its origin root.

Every internal URL goes through url() or localePath() in src/i18n.ts, so those two variables are the only things that have to know where the site lives. To move to a custom domain: clear SITE_BASE_PATH, point SITE_URL at the domain, and add a CNAME file to static/ so it survives every deploy.


🌏 Translating

Translation is partial by design. A chapter that has not been translated falls back to the English body with a banner saying so, which keeps /zh/ complete and navigable at every point instead of shipping a half-built second site.

To add a language:

  1. Add its code to LOCALES in src/i18n.ts and fill in LOCALE_META.

  2. Add a dictionary beside en and zh in the same file. That covers the site chrome, the landing copy and the curriculum layer names:

    Where What it holds
    CATALOGUE Nav, footer, rail, quiz UI, section labels, the fallback banner
    LANDING Home-page prose — hero, cards, curriculum intro, CTAs
    LAYER_TEXT Layer names, blurbs and the bridge sentence between layers
  3. Translate chapters into content/<code>/chapters/ and register them in content/<code>/index.ts. A translated chapter spreads the English one and overrides the text, so it inherits the diagrams and the runnable-file references rather than duplicating them:

    import en, { DIAL_SVG } from "../../chapters/c00.ts";
    const chapter: Chapter = { ...en, title: "智能体到底是什么", sections: [ /* … */ ] };
  4. Run npm run check, then npm run build. The build asserts every translation keeps the original's shape — same section, exercise, Q&A and quiz counts, and identical quiz answer indices.

Shape is not freshness, though, and the second one is what actually rots. Editing an English paragraph leaves its translation untouched and every check passing, so content/zh/translation-lock.json records a hash of the English behind each translated field:

npm run lock              # after re-translating, record what you translated from
npm run check:i18n        # fail if any English source moved (runs inside `verify`)

It distinguishes the two kinds of section. A spread section ({ ...explore }) is byte-identical to the English, so edits flow through and nothing is locked. A section with its own prose is locked, and an edit to the English side fails the check naming the exact field:

Chinese translations are out of date with the English source.
  The English changed; the translation did not:
    - c00 · section.core-idea

Simulator sections are deliberately shared with the English chapter. Their labels live inside the SVG and the client script, and splitting them would decouple the simulator from the code/ file it mirrors.

Conventions. Code identifiers (stopReason, tool_use, apply_patch), model and framework names, and paper titles stay in English — they are what the reader will type or search for. Everything else is translated.


Contributing

Issues and PRs welcome. Particularly useful:

  • Corrections. If a claim is wrong, open an issue with the evidence. This is the most valuable contribution there is.
  • Translations. See above — the build will tell you exactly what is missing.
  • Quiz questions. Six per chapter; more good ones are always welcome.
  • A chapter this course is missing. Voice agents, computer use, RL-trained tool use and cost-aware model routing are all absent and all interesting.

Credits

Built on the work of the people who actually solved these problems. Particular debts to Anthropic's Building Effective Agents, OpenAI Codex's apply_patch, Microsoft's autogen-core runtime, the Model Context Protocol specification, and Simon Willison's writing on prompt injection and the lethal trifecta. Chapter references are on the references page.

This is an educational reimplementation and is not affiliated with any of them. Production frameworks are the real thing; this teaches you how to read them.


Start with C00 →

If this helped, a ⭐ makes it findable for the next person.

GitHub · X · MIT licensed

About

The ReAct loop, tools, context engineering, memory, planning, MCP, evals and security — built from scratch in dependency-free TypeScript.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages