# Learn Claude Code — Admin & Architecture Guide

This guide is the deep reference for developers, architects, and contributors working on **Learn Claude Code** — and for anyone deploying its web platform. It documents the harness architecture each lesson builds, the on-disk state each mechanism uses, the web application's structure and build pipeline, the security model (including what is *deliberately* left unguarded), code patterns, testing, and known limitations.

For installation and how to *take* the course, see the companion **User Guide**. This document assumes you want to understand or extend the implementation.

---

## 1. Executive Summary

**What it is.** A 0-to-1 curriculum in *harness engineering* — the discipline of building the operational environment around an agent model. The project's thesis: agency comes from the *model* (trained weights), while the *harness* (tools, knowledge, observation, action interfaces, permissions) is what engineers build. An agent product = model + harness.

**What it contains.** Two coordinated deliverables:

1. **A Python course** — twenty standalone, runnable agent programs (`s01_agent_loop/code.py` … `s20_comprehensive/code.py`). Each is the previous program plus exactly one new harness mechanism, layered around an agent loop that never changes.
2. **A web platform** (`web/`) — a Next.js 16 static site that renders the course with interactive per-mechanism visualizations, code diffs, and architecture diagrams, in English / Chinese / Japanese.

**Two tracks.** The canonical **current** track is the root-level `s01`–`s20` folders (20 mechanisms). A **legacy** 12-lesson track survives in `agents/` and `docs/`, and the live web app currently renders that legacy track. Chapter numbers differ between tracks.

**Tech footprint.** Python ≥ 3.10 with three dependencies (`anthropic`, `python-dotenv`, `pyyaml`). The web app is Next.js 16 / React 19 / Tailwind 4, statically exported. No database, no server runtime for the lessons — **the filesystem is the only persistence layer** (`.memory/`, `.tasks/`, `.mailboxes/`, `.worktrees/`, `.transcripts/`, `.scheduled_tasks.json`).

**Design center.** Every mechanism is *additive* and *legible*: carried-over code is labelled `# FROM sNN (unchanged)`, and each file documents in its module docstring exactly what it adds. Production concerns (rule-based permission governance, full hook bus, real MCP transport/OAuth, session resume/fork) are intentionally simplified — the project is explicit that it teaches "key designs," not a production-grade clone.

---

## 2. System Architecture

### 2.1 The two halves

```
┌──────────────────────────────────────────────────────────────────────┐
│  TEACHING CODE (Python)                                                │
│                                                                        │
│   s01_agent_loop/code.py ── s02 ── s03 ── … ── s20_comprehensive       │
│        │ each = prev + one mechanism                                   │
│        │ talks to: Anthropic-compatible Messages API                   │
│        │ persists to: ./.memory ./.tasks ./.mailboxes ./.worktrees …   │
│                                                                        │
│   agents/  (legacy 12-lesson runnable copies + s_full.py)              │
│   skills/  (agent-builder, code-review, mcp-builder, pdf — used by s07)│
└──────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────┐
│  WEB PLATFORM (web/, Next.js 16, static export)                        │
│                                                                        │
│   docs/{en,zh,ja}/*.md ──► scripts/extract-content.ts (predev/prebuild)│
│        │                         │ generates web/src/data/generated/   │
│        ▼                         ▼                                      │
│   App Router  ──►  visualizations/  +  simulator/  +  diff/  +  i18n/   │
│   (static HTML out/)                                                   │
└──────────────────────────────────────────────────────────────────────┘
```

The two halves are loosely coupled: the web app consumes the `docs/` markdown (legacy track) at build time via an extraction script; the Python lessons have no dependency on the web app.

### 2.2 The canonical agent loop

Every lesson is built around one invariant pattern (this is the literal core; later lessons wrap it but never alter its shape):

```python
def agent_loop(messages):
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM,
            messages=messages, tools=TOOLS, max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason != "tool_use":     # model is done
            return

        results = []
        for block in response.content:
            if block.type == "tool_use":
                output = TOOL_HANDLERS[block.name](**block.input)
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output,
                })
        messages.append({"role": "user", "content": results})   # feed back, loop
```

The model decides when to call tools and when to stop (`stop_reason`); the harness only executes what's asked and feeds results back. The `tool_use` / `tool_result` contract (matching `tool_use_id`) is the load-bearing protocol — several known issues (see §11) stem from accidentally breaking that pairing during compaction.

### 2.3 The layered mechanism model

The web app's `web/src/lib/constants.ts` defines the authoritative grouping of the twenty mechanisms into five architectural layers. This is the project's own conceptual map:

| Layer | Color | Mechanisms |
|---|---|---|
| **Tools & Execution** | `#3B82F6` | s01, s02, s03, s04 |
| **Planning & Control** | `#10B981` | s05, s06, s07, s10, s11 |
| **Memory Management** | `#8B5CF6` | s08, s09 |
| **Concurrency & Scheduling** | `#F59E0B` | s13, s14 |
| **Multi-Agent Platform** | `#EF4444` | s12, s15, s16, s17, s18, s19, s20 |

The same file also stores per-version metadata (`title`, `subtitle`, `coreAddition`, `keyInsight`, `layer`, `prevVersion`) for all twenty chapters and the canonical `VERSION_ORDER` (`s01`…`s20`) — this is the single source the web UI uses to render the learning path and navigation.

---

## 3. Repository Structure

```
learn-claude-code/
├── README.md / README-zh.md / README-ja.md   # project thesis + chapter index
├── LICENSE                                    # MIT
├── requirements.txt                           # anthropic, python-dotenv, pyyaml
├── .env.example                               # required+optional env, provider table
│
├── s01_agent_loop/ … s20_comprehensive/       # CURRENT 20-lesson track
│     ├── README.md         # Chinese narrative (source)
│     ├── README.en.md      # English translation
│     ├── README.ja.md      # Japanese translation
│     ├── code.py           # standalone runnable agent
│     └── images/           # SVG diagrams (some chapters)
│   (s05_todo_write/example/hello.py — a sample target file)
│
├── agents/                                    # LEGACY 12-lesson runnable copies
│     ├── s01_agent_loop.py … s12_worktree_task_isolation.py
│     ├── s_full.py          # legacy "everything" agent
│     └── __init__.py
│
├── skills/                                    # SKILL.md packs loaded by s07
│     ├── agent-builder/  (+ references/, scripts/)
│     ├── code-review/
│     ├── mcp-builder/
│     └── pdf/
│
├── docs/{en,zh,ja}/                           # LEGACY 12-lesson docs (web source)
│     └── s01-the-agent-loop.md … s12-worktree-task-isolation.md
│
├── tests/                                     # pytest smoke tests
│     ├── test_agents_smoke.py
│     ├── test_s_full_background.py
│     └── test_todo_write_string_input.py
│
└── web/                                        # Next.js 16 learning platform
      ├── next.config.ts        # output:"export", trailingSlash, images.unoptimized
      ├── package.json          # predev/prebuild → npm run extract
      ├── scripts/extract-content.ts
      └── src/
            ├── app/[locale]/(learn)/…          # App Router, localized routes
            ├── components/visualizations/      # per-mechanism animated viz (entry: index.tsx)
            ├── components/{simulator,diff,timeline,architecture,code,docs,ui,layout}/
            ├── data/{annotations,scenarios,generated}/
            ├── i18n/messages/{en,zh,ja}.json
            └── lib/{constants.ts, i18n.tsx, i18n-server.ts, utils.ts}
```

> [!NOTE]
> The legacy-to-current chapter mapping is documented in the root `README.md`. Notable shifts: legacy `s03`→current `s05` (TodoWrite), legacy `s06`→current `s08` (Context Compact), legacy `s07`→current `s12` (Task System), legacy `s12`→current `s18` (Worktree). Mechanisms `s03`, `s04`, `s09`, `s10`, `s11`, `s14`, `s19`, `s20` are new in the current track.

---

## 4. The Harness — Architecture Deep Dive

This section documents each mechanism's internals: the data structures it introduces, the on-disk state it owns, and the integration point in the loop. Mechanisms compose additively, so later chapters inherit everything described earlier.

### 4.1 Tools & Execution (s01–s04)

**s01 — Agent Loop.** One tool (`bash`), one `while` loop. `run_bash` runs `subprocess.run(shell=True, timeout=120)`, returns combined stdout+stderr truncated to 50 000 chars, with a tiny inline deny-list. `MODEL` is read from `os.environ["MODEL_ID"]` (hard requirement); the client is `Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))`.

**s02 — Tool Use.** Adds `read_file`, `write_file`, `edit_file`, `glob`, and the central `TOOL_HANDLERS` dispatch map. Introduces `safe_path()` — resolves a path against `WORKDIR` and raises `ValueError("Path escapes workspace")` if it escapes. The loop changes one line: `output = TOOL_HANDLERS[block.name](**block.input)`.

**s03 — Permission.** A three-gate pipeline before execution:
- **Gate 1 — deny-list:** `DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if=", "> /dev/sda"]` (bash only).
- **Gate 2 — rule matching:** `PERMISSION_RULES` flag writes outside the workspace and destructive bash keywords (`rm `, `> /etc/`, `chmod 777`).
- **Gate 3 — user approval:** interactive `Allow? [y/N]` prompt.
Integration is one line: `if not check_permission(block): continue`.

**s04 — Hooks.** Extracts cross-cutting logic onto a `HOOKS` registry keyed by event: `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Stop`. `register_hook(event, cb)` / `trigger_hooks(event, *args)`; a hook returning non-`None` short-circuits (used to block a tool call). The s03 permission check is reimplemented as a `PreToolUse` hook. The loop no longer contains the permission logic — it calls `trigger_hooks("PreToolUse", block)`.

### 4.2 Planning & Control (s05, s06, s07, s10, s11)

**s05 — TodoWrite.** Adds the `todo_write` tool over in-memory `CURRENT_TODOS` (each item `{content, status}` where status ∈ `pending|in_progress|completed`). `_normalize_todos` accepts a list or a JSON/`ast.literal_eval` string (robustness against the model passing a stringified array — see the dedicated test). A "nag" counter injects `<reminder>Update your todos.</reminder>` after 3 tool rounds without a `todo_write`.

**s06 — Subagent.** Adds the `task` tool → `spawn_subagent(description)`. The sub-agent runs its own loop with a **fresh** `messages=[{"role":"user","content":description}]`, a distinct `SUB_SYSTEM`, and a restricted `SUB_TOOLS` set that **excludes `task`** (no recursion). Capped at **30 turns**. Returns only the final text (with a fallback search backward for the last assistant text, else `"Subagent stopped after 30 turns…"`). Intermediate history is discarded — this is the context-isolation lesson.

**s07 — Skill Loading.** Two-level knowledge injection. At startup `_scan_skills()` walks `skills/*/SKILL.md`, parses YAML frontmatter (`name`, `description`), and builds `SKILL_REGISTRY`. The system prompt carries only names+descriptions (cheap, ~100 tokens each via `build_system()`); the full body is fetched on demand by the `load_skill(name)` tool (registry lookup — **no path traversal**). Bundled skills: `agent-builder`, `code-review`, `mcp-builder`, `pdf`.

**s10 — System Prompt.** Runtime prompt assembly. `PROMPT_SECTIONS` (identity, tools, workspace, memory) are selected and joined by `assemble_system_prompt(context)` based on real state (e.g. memory section loads only if `.memory/MEMORY.md` exists). `get_system_prompt(context)` caches via `json.dumps(context, sort_keys=True)` — the docstring explicitly notes Python's `hash()` is unsuitable (process randomization, fails on nested structures). `update_context()` re-derives context after each tool round.

**s11 — Error Recovery.** Wraps the model call in classified recovery:
- **Path 1 — `max_tokens`:** escalate `8000 → 64000` (retry without appending truncated output), then up to `MAX_RECOVERY_RETRIES=3` continuation prompts.
- **Path 2 — prompt too long:** `reactive_compact` once (trims to last 5 messages with a marker).
- **Path 3 — 429/529:** `with_retry` does exponential backoff with jitter (`BASE_DELAY_MS=500`, cap 32 s, `MAX_RETRIES=10`); after `MAX_CONSECUTIVE_529=3` it switches to `FALLBACK_MODEL_ID` if set.
State lives in a `RecoveryState` object. Raises `RuntimeError("Max retries … exceeded")` at the ceiling.

### 4.3 Memory Management (s08, s09)

**s08 — Context Compact.** A four-layer pipeline run *before* each model call, "cheap first, expensive last" (order matches Claude Code's source: budget → snip → micro → auto):
- **L1 `snip_compact`** — when `len(messages) > 50`, keep 3 head + tail, replace the middle with a `[snipped N messages]` marker.
- **L2 `micro_compact`** — replace all but the last `KEEP_RECENT=3` tool results (>120 chars) with a placeholder.
- **L3 `tool_result_budget`** — when the latest turn's tool results exceed 200 KB, spill the largest (> `PERSIST_THRESHOLD=30000`) to `.task_outputs/tool-results/<id>.txt` and leave a `<persisted-output>` preview.
- **L4 `compact_history`** — if `estimate_size(messages) > CONTEXT_LIMIT=50000`, write a transcript to `.transcripts/` and replace history with a one-call LLM summary.
- **Emergency `reactive_compact`** — on an API "prompt too long", summarize + keep the last 5 messages, retry once.
Adds a `compact` tool that triggers L4 on demand.

**s09 — Memory.** Persistent cross-session knowledge in `.memory/`. Three subsystems:
- **Selection** — `select_relevant_memories()` asks the model (with keyword fallback) which memory files match the recent conversation; `load_memories()` injects the chosen files' content into the current turn.
- **Extraction** — after each turn, `extract_memories()` runs over a *pre-compression snapshot* and writes new `{name, type, description, body}` files (types: `user|feedback|project|reference`) with YAML frontmatter; `_rebuild_index()` keeps `MEMORY.md` (one line per memory) in sync.
- **Consolidation** — when files ≥ `CONSOLIDATE_THRESHOLD=10`, `consolidate_memories()` merges duplicates / drops stale entries via an LLM call, targeting ≤30 memories.
The `MEMORY.md` index is injected into the system prompt every turn (cheap); full bodies load on demand.

### 4.4 Concurrency & Scheduling (s13, s14)

**s13 — Background Tasks.** `threading.Thread` daemon execution. `should_run_background()` honors an explicit `run_in_background` tool param, else falls back to `is_slow_operation()` (keyword heuristic: `install`, `build`, `test`, `deploy`, `pip install`, `npm install`, `pytest`, `make`, …). `start_background_task()` dispatches to a daemon worker, returns a `bg_NNNN` id immediately, and stores results under a `threading.Lock`. `collect_background_results()` drains completed jobs and injects `<task_notification>` blocks into the next user message — deliberately **not** reusing the original `tool_use_id`.

**s14 — Cron Scheduler.** Four cooperating layers:
1. **Scheduler thread** (`cron_scheduler_loop`) polls every 1 s, matches jobs, and appends fired jobs to `cron_queue`. Uses a date-aware `"%Y-%m-%d %H:%M"` marker (`_last_fired`) so a job fires at most once per calendar minute. Per-job errors are caught so one bad job can't kill the thread.
2. **Queue** — `cron_queue` decouples the scheduler from the agent.
3. **Queue processor** (`queue_processor_loop`) wakes the agent (under `agent_lock`, non-blocking acquire) when work is queued and the agent is idle.
4. **Consumer** — `agent_loop` drains `consume_cron_queue()` and injects `[Scheduled] <prompt>` messages.
`CronJob(id, cron, prompt, recurring, durable)`. `cron_matches` implements 5-field matching with `*`, `*/step`, ranges, and lists, and **standard DOM/DOW OR semantics**. `validate_cron` bounds-checks each field. Durable jobs persist to `.scheduled_tasks.json` and reload (with re-validation) on startup.

### 4.5 Multi-Agent Platform (s12, s15–s20)

**s12 — Task System.** The durable foundation for multi-agent work. `Task(id, subject, description, status, owner, blockedBy)` persisted one-JSON-file-per-task in `.tasks/`. `can_start()` treats missing dependencies as blocked; `claim_task()` sets `owner` + `in_progress` (rejecting already-claimed); `complete_task()` reports newly unblocked downstream tasks. Five tools: `create_task`, `list_tasks`, `get_task`, `claim_task`, `complete_task`.

**s15 — Agent Teams.** A file-based `MessageBus` over `.mailboxes/*.jsonl`. `spawn_teammate_thread` runs a teammate in a background thread with its own simplified loop (`bash, read, write, send_message`); teammate→Lead messages are injected into the Lead's history (not merely printed). Permissions "bubble" — teammates run the same `PreToolUse` hooks. (Teaching version caps teammate rounds; production uses an idle loop, introduced next.)

**s16 — Team Protocols.** Request/response contracts with correlation. `ProtocolState(request_id, type, sender, status, created_at)` + a `pending_requests` registry. `dispatch_message` routes by type to handlers (`handle_shutdown_request`, `handle_plan_response`, …); `match_response` correlates replies by `request_id` with type validation. Adds shutdown handshake and plan request/approval; teammates switch to an **idle loop** (wait for inbox) instead of exiting after N rounds.

**s17 — Autonomous Agents.** Self-organization. `scan_unclaimed_tasks` finds pending, unowned tasks whose deps are complete; teammates auto-claim and run a **WORK → IDLE → SHUTDOWN** lifecycle (idle poll ~5 s; shutdown dispatch when idle ~60 s). Identity is re-injected after context compression so a teammate doesn't forget who it is.

**s18 — Worktree Isolation.** Filesystem isolation to match the context isolation. `Task` gains a `worktree: str | None` field. `validate_worktree_name` rejects path traversal and illegal chars (1–40 chars: letters, digits, `.`, `_`, `-`). `create_worktree` does `git worktree add` under `.worktrees/<name>` on branch `wt/<name>`; tasks bind by name; an event log is written to `.worktrees/events.jsonl`. `run_git` returns `(ok, output)` and logs events only on success. Requires a git repo (`RuntimeError` otherwise). Tools: `create_worktree`, `remove_worktree`, `keep_worktree`.

**s19 — MCP Plugin.** External-tool bridge. `MCPClient` discovers tools from a server and `assemble_tool_pool` merges builtin + MCP tools into one dynamic pool. Tool names are normalized to `mcp__{server}__{tool}`; MCP tools carry `readOnly`/`destructive` annotations. Because the pool is dynamic, prompt caching is disabled for the assembled tools. (The transport here is a **mock** handler; real MCP transport, OAuth, resource subscription, and polling are out of scope — see §11.)

**s20 — Comprehensive Agent.** The integration finale: every mechanism above in one harness around the same loop. It re-tunes some constants for the combined setting (e.g. `ESCALATED_MAX_TOKENS=16000`, `MAX_RETRIES=3`, `MAX_CONSECUTIVE_529=2`) and adds a thread-safe `terminal_print` that redraws the prompt line when background threads emit output during interactive use.

---

## 5. The Web Platform

### 5.1 Stack & build model

| Concern | Choice |
|---|---|
| Framework | **Next.js 16.1.6** (App Router) |
| UI | **React 19.2.3**, **Tailwind CSS 4**, **framer-motion 12** (animation), **lucide-react** (icons) |
| Markdown | `unified` + `remark-parse` / `remark-gfm` / `remark-rehype` + `rehype-raw` / `rehype-highlight` / `rehype-stringify` |
| Diffing | `diff` 8 |
| Output | **Static export** (`output: "export"`, `trailingSlash: true`, `images.unoptimized: true`) → plain HTML in `out/` |
| Scripting | `tsx` runs `scripts/extract-content.ts` |

`next.config.ts`:

```ts
const nextConfig: NextConfig = {
  output: "export",
  images: { unoptimized: true },
  trailingSlash: true,
};
```

Because the site is a static export, it has **no server runtime** — it can be hosted on any static host or CDN.

### 5.2 Content pipeline

`package.json` wires a pre-step into both dev and build:

```json
"scripts": {
  "extract": "tsx scripts/extract-content.ts",
  "predev":  "npm run extract",
  "dev":     "next dev",
  "prebuild":"npm run extract",
  "build":   "next build",
  "start":   "next start"
}
```

`npm run dev` and `npm run build` therefore always regenerate extracted content first (into `web/src/data/generated/`) from the `docs/` markdown. Operators never run extraction manually.

> [!IMPORTANT]
> The web app currently sources the **legacy `docs/` (s01–s12)** track, while the canonical learning material is the root-level `s01`–`s20` folders. Keep this in mind when reconciling what the site shows against the current chapters.

### 5.3 Internationalization

A lightweight, dependency-free i18n: JSON message catalogs at `web/src/i18n/messages/{en,zh,ja}.json`, a client `I18nProvider`/`useTranslations` context (`lib/i18n.tsx`), and a server variant (`lib/i18n-server.ts`). Both fall back to English on a missing key, then to the raw key. Routes are localized under `app/[locale]/(learn)/…`.

### 5.4 Visualization architecture

`web/src/components/visualizations/index.tsx` is the entry point: a `Record<version, React.lazy(...)>` map (s01–s20) wrapped in `Suspense`, so each mechanism's animated diagram is **code-split** and loaded on demand. `lib/constants.ts` drives the navigation (`VERSION_ORDER`, `VERSION_META`, `LAYERS`). Supporting component groups: `simulator/` (interactive agent simulation), `diff/` (chapter-to-chapter code diffs), `timeline/`, `architecture/`, `code/`, `docs/`, plus `ui/` and `layout/` primitives. Data lives in `web/src/data/{annotations,scenarios,generated}/`.

---

## 6. Requirements & Configuration Reference

### 6.1 Environment variables

| Variable | Required | Read by | Purpose |
|---|---|---|---|
| `ANTHROPIC_API_KEY` | **Yes** | Anthropic SDK (implicit) | API credential (or provider key when `ANTHROPIC_BASE_URL` is set) |
| `MODEL_ID` | **Yes** | `os.environ["MODEL_ID"]` (every lesson) | Model to call; default `claude-sonnet-4-6` |
| `ANTHROPIC_BASE_URL` | No | `os.getenv` (s07+ and all later) | Point at an Anthropic-compatible provider |
| `FALLBACK_MODEL_ID` | No | s11, s20 | Model to switch to after repeated 529 overloads |

> [!NOTE]
> When `ANTHROPIC_BASE_URL` is set, the lessons proactively `os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)` so the provider key in `ANTHROPIC_API_KEY` is authoritative.

### 6.2 Provider matrix (from `.env.example`)

| Provider | `MODEL_ID` | International `ANTHROPIC_BASE_URL` | China-mainland base URL |
|---|---|---|---|
| Anthropic | `claude-sonnet-4-6` | *(default)* | *(default)* |
| MiniMax | `MiniMax-M2.5` | `https://api.minimax.io/anthropic` | `https://api.minimaxi.com/anthropic` |
| GLM (Zhipu) | `glm-5` | `https://api.z.ai/api/anthropic` | `https://open.bigmodel.cn/api/anthropic` |
| Kimi (Moonshot) | `kimi-k2.5` | `https://api.moonshot.ai/anthropic` | `https://api.moonshot.cn/anthropic` |
| DeepSeek | `deepseek-chat` | `https://api.deepseek.com/anthropic` | *(same global endpoint)* |

### 6.3 On-disk state (created in the working directory)

| Path | Owner mechanism | Contents |
|---|---|---|
| `.memory/` + `MEMORY.md` | s09 | Memory files (md + YAML frontmatter) and the index |
| `.tasks/task_*.json` | s12+ | One JSON file per task |
| `.scheduled_tasks.json` | s14 | Durable cron jobs |
| `.mailboxes/*.jsonl` | s15+ | Teammate message bus |
| `.worktrees/<name>` + `.worktrees/events.jsonl` | s18 | Git worktrees + event log |
| `.transcripts/transcript_*.jsonl` | s08+ | Pre-compaction history snapshots |
| `.task_outputs/tool-results/<id>.txt` | s08+ | Spilled large tool outputs |

> [!TIP]
> To reset any lesson to a clean state, delete the relevant dot-directories from your working directory. They are regenerated on demand.

---

## 7. Deployment (Web Platform)

The lessons are not "deployed" — they run locally. Only the web platform is deployable, and because it is a static export, deployment is straightforward.

```bash
cd web
npm install
npm run build          # runs extract → next build → static HTML in out/
```

Deployment checklist:

- [ ] Node.js (current LTS) and npm available on the build host.
- [ ] `npm run build` completes and produces `web/out/`.
- [ ] Serve `web/out/` from any static host or CDN (e.g. a bucket + CDN, Nginx, GitHub Pages, Vercel static).
- [ ] Because `trailingSlash: true`, ensure your host serves `path/` → `path/index.html`.
- [ ] Because `images.unoptimized: true`, no image-optimization server is needed.
- [ ] Verify all three locales (`/en`, `/zh`, `/ja`) render.

> [!CAUTION]
> There is no backend, no auth, and no secrets in the web build — it is purely static content. Do **not** add the Python `.env` (API keys) anywhere into `web/`; the website never calls the model API.

---

## 8. Security Analysis

This is a *teaching* harness. Several mechanisms are exactly the security controls being taught — and the project is explicit that some production protections are intentionally omitted. Treat the agent as a program that runs arbitrary shell commands on your machine.

### 8.1 Controls that exist

| Control | Where | What it stops |
|---|---|---|
| **Hard deny-list** | s03/s04+ (`DENY_LIST`) | `rm -rf /`, `sudo`, `shutdown`, `reboot`, `mkfs`, `dd if=`, raw device writes |
| **Destructive-command prompt** | s03/s04 | `rm `, `> /etc/`, `chmod 777` → interactive approval |
| **Workspace sandboxing** | s02+ (`safe_path`) | File reads/writes resolving outside `WORKDIR` → `ValueError: Path escapes workspace` |
| **Worktree name validation** | s18 (`validate_worktree_name`) | Path traversal / illegal characters in worktree names |
| **Skill lookup by registry** | s07 (`load_skill`) | Path traversal via skill names (registry lookup, not filesystem path join) |
| **Sub-agent recursion cap** | s06 | Runaway recursion (no `task` tool in `SUB_TOOLS`; 30-turn limit) |
| **MCP tool annotations** | s19 | Surfaces `readOnly`/`destructive` so a harness can gate accordingly |

### 8.2 Intentional gaps (do not rely on this as a sandbox)

The project's own "Scope" section lists these as deliberately simplified or omitted:

- **Full event/hook bus** (`PreToolUse`, `SessionStart/End`, `ConfigChange`) — only minimal lifecycle events are wired.
- **Rule-based permission governance and trust workflows** — the deny-list + interactive approval is a teaching subset, not a policy engine.
- **Session lifecycle controls** (resume/fork) and complete worktree lifecycle handling.
- **Real MCP runtime** — transport, OAuth, resource subscription, and polling are mocked.
- The **JSONL mailbox protocol** is a teaching implementation, not a claim about any production internal.

> [!WARNING]
> `subprocess.run(shell=True, …)` executes whatever the model emits. The deny-list is substring-based and easily bypassed by a determined or confused model (e.g. obfuscated commands). **Run lessons in a disposable directory / VM**, never against production systems or sensitive data. This is acceptable for a learning tool and is itself part of what the permission chapters teach you to harden.

### 8.3 Secret handling

Credentials live only in the Python-side `.env` (gitignored). The web build contains no secrets and makes no API calls. There is no telemetry; lessons talk only to the configured model endpoint.

---

## 9. Code Quality & Patterns

- **Additive, legible layering.** Each `code.py` is the previous file plus one section, with carried-over code explicitly marked `# FROM sNN (unchanged)` and a module docstring enumerating the diff. This makes the codebase a *teaching artifact* where a chapter-to-chapter `diff` is the lesson.
- **Dispatch-map extensibility.** Capabilities register into `TOOL_HANDLERS`; the loop body is invariant. Adding a tool = add an entry, not edit the loop.
- **Filesystem as state machine.** No DB, no broker. Tasks, memory, mailboxes, worktrees, schedules, and transcripts are all plain files/dirs — durable, inspectable, and resettable by deletion. (This mirrors a pattern production agents also use.)
- **Dataclasses for records.** `Task`, `CronJob`, `ProtocolState`, `RecoveryState` are dataclasses serialized with `asdict` + `json`.
- **Defensive parsing.** `_normalize_todos` accepts both real lists and stringified JSON/Python; `_parse_frontmatter` tolerates missing/invalid YAML; cron fields are bounds-validated before use.
- **Threading model.** Background tasks (s13), the cron scheduler/queue processor (s14), and teammates (s15+) all use daemon threads with `threading.Lock` around shared dicts and an `agent_lock` to serialize agent turns.
- **Deterministic prompt caching.** s10 caches the assembled system prompt via `json.dumps(sort_keys=True)` rather than `hash()` (documented rationale: process-randomized hashing and unhashable nested structures).

**Conventions:** Python ≥ 3.10 syntax (`str | None`, `Path.is_relative_to`); ANSI color in terminal output; module docstrings carry ASCII architecture diagrams; bilingual prompts (some chapters print Chinese REPL hints).

---

## 10. Testing Strategy

`tests/` contains focused **smoke tests** (pytest), not exhaustive coverage:

| Test | Verifies |
|---|---|
| `test_agents_smoke.py` | The legacy `agents/` modules import/run end-to-end without crashing |
| `test_s_full_background.py` | The legacy "everything" agent's background-task path |
| `test_todo_write_string_input.py` | `todo_write` accepts a stringified-JSON `todos` arg (the `_normalize_todos` robustness path) |

> [!NOTE]
> A community "Production Readiness Scan" (issue #169) scored the repo low on testing/error-handling — by design. These are teaching programs optimized for clarity, not a production library; the test suite is a sanity net, not a guarantee. Contributors adding mechanisms should add at least a smoke test that the lesson imports and completes a trivial turn.

---

## 11. Known Limitations & Improvement Areas

Drawn from the project's "Scope" section and its open community issues:

- **Compaction can split `tool_use`/`tool_result` pairs** (issues #325, and the `400 BadRequest` reports #345/#55). Breaking that pairing makes the next request invalid. A robust fix keeps each `tool_use` block adjacent to its matching `tool_result` when trimming/snipping.
- **`400 BadRequest` in s13 background tasks** (#345) — likely the same pairing problem under concurrent notification injection.
- **`s09` memory `build_system()` placement** (#343) — noted as being rebuilt inside a loop where it could be hoisted; an efficiency cleanup.
- **`s10` `build_system_reminder` usage** (#279) — example code references a reminder builder whose call site is incomplete.
- **Teammate edge cases** (#291) — a Lead can exit early if a teammate runs a long tool-less task and the Lead sees no tool calls; the idle/shutdown heuristics need tightening for long teammate work.
- **Context-compression vs. task-system design discussion** (#174) — open design debate about the s08/s12 trade-offs.
- **Intentionally simplified for teaching:** full hook bus, rule-based permission governance, session resume/fork, complete worktree lifecycle, and real MCP transport/OAuth/subscription. These are the natural "productionization" exercises after the course.
- **Permission deny-list is substring-based** — adequate for teaching, trivially bypassable in adversarial use; a real harness needs structured command parsing and policy rules.

> [!TIP]
> The README also points to the maintainers' productization follow-ons (a CLI coding agent and an embeddable agent SDK) and a sister tutorial on "always-on" harnesses (heartbeat + cron + IM channels + memory + personality) — the recommended next steps once the twenty mechanisms are understood.

---

## 12. Quick Reference

**Run a lesson:** `python sNN_<name>/code.py` · quit with `q`/`exit`/empty line.
**Required env:** `ANTHROPIC_API_KEY`, `MODEL_ID` (default `claude-sonnet-4-6`). **Optional:** `ANTHROPIC_BASE_URL`, `FALLBACK_MODEL_ID`.
**Deps:** `pip install -r requirements.txt` (`anthropic`, `python-dotenv`, `pyyaml`).
**Web:** `cd web && npm install && npm run dev` (→ `:3000`) · build: `npm run build` (→ `web/out/`).

**Mechanism → tools added:**

| Chapter | Adds | New tools |
|---|---|---|
| s01 | Agent loop | `bash` |
| s02 | Tool dispatch | `read_file`, `write_file`, `edit_file`, `glob` |
| s03 | Permission gates | — |
| s04 | Hook registry | — |
| s05 | Planning | `todo_write` |
| s06 | Sub-agents | `task` |
| s07 | Skills | `load_skill` |
| s08 | Compaction | `compact` |
| s09 | Memory | — (`.memory/`) |
| s10 | Prompt assembly | — |
| s11 | Error recovery | — |
| s12 | Task board | `create_task`, `list_tasks`, `get_task`, `claim_task`, `complete_task` |
| s13 | Background exec | `run_in_background` param on `bash` |
| s14 | Cron | `schedule_cron`, `list_crons`, `cancel_cron` |
| s15 | Teams | `spawn_teammate`, `send_message`, `check_inbox` |
| s16 | Protocols | `request_shutdown`, `request_plan`, `review_plan`, `submit_plan` |
| s17 | Autonomy | (teammate gains `list_tasks`, `claim_task`, `complete_task`) |
| s18 | Worktrees | `create_worktree`, `remove_worktree`, `keep_worktree` |
| s19 | MCP | `mcp__{server}__{tool}` (dynamic) |
| s20 | Everything | all of the above |

**On-disk state:** `.memory/` · `.tasks/` · `.scheduled_tasks.json` · `.mailboxes/` · `.worktrees/` · `.transcripts/` · `.task_outputs/`.

**License:** MIT.

---

_Generated June 2026 from shareAI-lab/learn-claude-code at ec9ea87. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
