You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Republish claude_code_nano after the style/branding sweep
Refresh the course site (de-branded lecture pages + landing, rebuilt
starter.zip) and drop "CS61A-style" from the projects-buffer anchor
description ("build your own Claude Code, a guided project").
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012nqHzGMvCaDtp7MYU1dGqw
<h2id="a-cs61a-style-lecture-for-builders-of-turn-based-simulators-reading-their-first-tool-contract">A CS61A-Style Lecture for Builders of Turn-Based Simulators, Reading Their First Tool Contract</h2>
14
13
<hr>
15
14
<h2id="lecture-overview">Lecture Overview</h2>
16
-
<p>Lecture 01 was a spec-reading exercise — you traced a conversation, answered questions, and fixed one warm-up bug that had nothing to do with agents. Today you write the first real function of the course: <code>runTool</code>, the piece of <code>index.ts</code> that turns a model's <em>request</em> into something that actually happens on your machine.</p>
17
-
<p>Recall the two-part definition from lecture 01: an agent is a model that can ask, plus a harness that runs and reports back. <code>TOOLS</code> — the schema array you read last time — is the "ask" half, already fully written, GIVEN, not yours to touch. <code>runTool</code> is the "run" half, and right now it's a single line: <code>throw new Error("YOUR CODE HERE")</code>. Every test in <code>tests/02_read_only_tools.test.ts</code> calls <code>runTool</code> directly — no model, no loop, no network — so today is entirely about that one function.</p>
18
-
<p>You'll implement <code>runTool</code> as a <strong>dispatch table</strong>: a <code>switch</code> on the tool's <code>name</code> that routes to the right implementation, with a <code>default</code> case that refuses anything it doesn't recognize. This lecture builds two of its five branches — <code>read_file</code> and <code>list_files</code>, the tools that only look, never change anything. <code>write_file</code>, <code>edit_file</code>, and <code>bash</code> — the branches that touch disk and process — are lecture 03's job, added into this <em>same</em> switch statement.</p>
19
-
<divclass="table-wrap"><table><thead><tr><th>Tool</th><th>Half already given (<code>TOOLS</code>)</th><th>Half you write (<code>runTool</code>)</th></tr></thead><tbody><tr><td><code>read_file</code></td><td>done (lecture 01)</td><td><strong>today</strong></td></tr><tr><td><code>list_files</code></td><td>done (lecture 01)</td><td><strong>today</strong></td></tr><tr><td><code>write_file</code></td><td>done (lecture 01)</td><td>lecture 03</td></tr><tr><td><code>edit_file</code></td><td>done (lecture 01)</td><td>lecture 03</td></tr><tr><td><code>bash</code></td><td>done (lecture 01)</td><td>lecture 03</td></tr></tbody></table></div>
20
-
<p><strong>How this lecture works:</strong> two numbered problems, 2.1 and 2.2, each with a spec, a worked example, constraints, and 2–3 unlock questions whose answers live only in the Appendix. Attempt each problem — write real code in <code>index.ts</code> — before checking its snippet. This lecture is worth <strong>2 of the course's 10 points</strong>: <code>./ok 02</code> green plus correct unlock answers.</p>
15
+
<p>This lecture writes the first real function of the course: <code>runTool</code>, the piece of <code>index.ts</code> that turns a model's tool-use request into something that actually happens on your machine. <code>TOOLS</code> — the schema array read in lecture 01 — is the "ask" half, already fully written; <code>runTool</code> is the "run" half, and right now it's a single line: <code>throw new Error("YOUR CODE HERE")</code>. Every test in <code>tests/02_read_only_tools.test.ts</code> calls <code>runTool</code> directly — no model, no loop, no network — so today is entirely about that one function.</p>
16
+
<p><code>runTool</code> is a <code>switch</code> on the tool's <code>name</code> that routes to the right implementation, with a <code>default</code> case that refuses anything it doesn't recognize. This lecture writes two of its five branches — <code>read_file</code> and <code>list_files</code>, the tools that only look, never change anything. <code>write_file</code>, <code>edit_file</code>, and <code>bash</code> — the branches that touch disk and process — go into this <em>same</em><code>switch</code> in lecture 03.</p>
17
+
<p><strong>How this lecture works:</strong> two numbered problems, 2.1 and 2.2, each with a spec, a worked example, constraints, and 2–3 unlock questions whose answers live only in the Appendix. Write real code in <code>index.ts</code> before checking each snippet. This lecture is worth <strong>2 of the course's 10 points</strong>: <code>./ok 02</code> green plus correct unlock answers.</p>
21
18
<hr>
22
19
<h2id="1-what-is-a-tool-really-two-halves">1. What Is a Tool, Really? Two Halves</h2>
23
20
<h3id="concept">Concept</h3>
@@ -26,8 +23,8 @@ <h3 id="concept">Concept</h3>
26
23
// plain function that RUNS it. The model never executes anything — it only
27
24
// asks; runTool() below is where things actually happen on this machine.
28
25
//
29
-
// DSA framing: a tool is an interface. The schema is the type signature the
30
-
// model programs against; runTool is the implementation behind it.</code></pre>
26
+
// A tool is a contract: the schema is the signature the model programs
27
+
// against; runTool is the implementation behind it.</code></pre>
31
28
<p>This is the same split you've used for years without naming it "agent architecture": an <strong>interface</strong> (a name, a signature, a contract — no body) versus an <strong>implementation</strong> (the body that actually does the work). <code>TOOLS[0]</code> — the <code>read_file</code> entry — is the interface: it tells the model "there's a function called <code>read_file</code>, it takes one required string field <code>path</code>, and here's a sentence describing what it does." Nothing in that entry touches a disk. The disk-touching happens forty lines lower, inside <code>runTool</code>, in code the model never sees and never runs itself.</p>
32
29
<h3id="why-it-matters">Why It Matters</h3>
33
30
<p>This split is why you can trust an LLM near your filesystem at all without also trusting it to <em>directly execute arbitrary code</em>. The model's output is always just text in a particular shape — a <code>tool_use</code> block asking for <code>read_file</code> with some <code>path</code>. That request is completely inert (lecture 01, §1's checkpoint) until your <code>runTool</code> — code you wrote, code you can read start to finish — decides what "running <code>read_file</code>" actually means. If <code>runTool</code>'s <code>read_file</code> case silently also deleted the file first, the model would never know; it only sees whatever string <code>runTool</code> hands back. The schema is a promise about <em>shape</em>; <code>runTool</code> is the only place that promise gets <em>kept</em>.</p>
<h2id="2-reading-the-contract-the-tools-array">2. Reading the Contract: The TOOLS Array</h2>
52
49
<h3id="concept-1">Concept</h3>
53
-
<p>Before writing <code>runTool</code>, read <code>TOOLS</code> closely — all of it, not just the two entries you'll implement today. It's the model-facing <strong>interface</strong> for all five tools, and it's already complete; your job in this section is comprehension, not code. Think of it the way you'd read a <code>.d.ts</code> file or an abstract base class before implementing its subclasses: every field in there is a constraint your implementation must satisfy, and every field it <em>omits</em> is freedom your implementation has.</p>
50
+
<p>Before writing <code>runTool</code>, read <code>TOOLS</code> closely — all of it, not just the two entries you'll implement today. It's the model-facing <strong>interface</strong> for all five tools, and it's already complete; your job in this section is comprehension, not code. Every field in there is a constraint your implementation must satisfy, and every field it <em>omits</em> is freedom your implementation has.</p>
54
51
<p>Two fields matter most for today's work:</p>
55
52
<ul><li><strong><code>required</code></strong> — <code>read_file</code>'s schema has <code>required: ["path"]</code>; <code>list_files</code>'s has <code>required: []</code>. That's not decoration. It's the contract's way of saying "the model must always supply a path to read a file, but may omit it when listing, and here's what happens if it does" (see <code>list_files</code>'s description: <code>"defaults to '.'"</code>).</li><li><strong><code>description</code></strong> — the <em>only</em> channel the model has for understanding what a tool does, since it never sees <code>runTool</code>'s source. Every English sentence in there is doing real work: "one level," "Directories end with '/'," "Call again on a subdirectory to go deeper" are all promises your <code>runTool</code> implementation is about to make true.</li></ul>
56
53
<h3id="why-it-matters-1">Why It Matters</h3>
@@ -73,7 +70,7 @@ <h3 id="concept-2">Concept</h3>
73
70
default:
74
71
// ...
75
72
}</code></pre>
76
-
<p>Each <code>case</code> is a branch; <code>default</code> is the fallback when the key matches nothing — the same role a hash map's "key not found" branch plays, or a state machine's "invalid transition" handler. <code>runTool</code>'s <code>default</code> case is not optional flavor: since <code>name</code> arrives as a plain <code>string</code> (not a restricted union type — <code>input: any</code> means TypeScript isn't checking any of this at compile time), <em>any</em> string could show up, including typos, or tool names from some future version of <code>TOOLS</code> you haven't written a case for yet. <code>default</code> is where you refuse those, loudly.</p>
73
+
<p>Each <code>case</code> is a branch; <code>default</code> is the fallback when <code>name</code>matches no case at all. <code>runTool</code>'s <code>default</code> case is not optional flavor: since <code>name</code> arrives as a plain <code>string</code> (not a restricted union type — <code>input: any</code> means TypeScript isn't checking any of this at compile time), <em>any</em> string could show up, including typos, or tool names from some future version of <code>TOOLS</code> you haven't written a case for yet. <code>default</code> is where you refuse those, loudly.</p>
77
74
<p><strong>TS-ism:</strong> each <code>case</code> here ends in a <code>return</code>, so there's no fallthrough to worry about (the classic C/Java <code>switch</code> footgun where control "falls through" to the next case unless you <code>break</code>). If a <code>case</code> doesn't <code>return</code>, control does fall through to the next one — but every branch you write today returns immediately, so you can set that concern aside for now.</p>
78
75
<h3id="why-it-matters-2">Why It Matters</h3>
79
76
<p>Five tools, one function, one <code>name</code> string deciding which of five completely different pieces of code runs. Get the dispatch wrong — misspell a case label, forget <code>default</code>, let a case fall through — and the failure mode is silent and specific: the model asks for <code>list_files</code>, and either nothing happens, the wrong tool runs, or an unrelated exception surfaces with no clue that dispatch was the problem. A dispatch table's whole value proposition is that adding a sixth tool later means adding one <code>case</code>, touching nothing else — which is exactly how lecture 03 will extend this same function without rewriting it.</p>
<ol><li><strong>A tool is a schema plus a function, and only one of those two you're allowed to touch today.</strong><code>TOOLS</code> is a fixed, model-facing contract; <code>runTool</code> is the implementation, and nothing checks that the two agree except careful reading on your part.</li></ol>
142
-
<ol><li><strong><code>switch (name) { ... default: ... }</code> is a dispatch table</strong> — the DSA structure behind "route to one of N implementations by a key," with <code>default</code> as the mandatory safety net for any tool name that isn't (yet, or ever) a real case.</li></ol>
139
+
<ol><li><strong><code>switch (name) { ... default: ... }</code> is a dispatch table</strong> — one entry point that routes to N implementations by a key, with <code>default</code> as the mandatory safety net for any tool name that isn't (yet, or ever) a real case.</li></ol>
143
140
<ol><li><strong>Node's <code>fs.readFileSync</code>/<code>fs.readdirSync</code> are synchronous and block until disk I/O completes</strong> — simple and correct for this single-user CLI; the <code>async</code>/<code>await</code> alternative and <em>why</em> it matters is lecture 04's job.</li></ol>
144
141
<ol><li><strong><code>{ withFileTypes: true }</code> turns filenames into <code>Dirent</code>s</strong>, the only way to know whether an entry is a file or a directory without a second <code>fs</code> call per entry.</li></ol>
145
142
<ol><li><strong><code>list_files</code> never recurses — the model does, one <code>tool_use</code> call at a time.</strong> "One level" in the schema and "no loop in <code>runTool</code>" are the same design decision seen from two sides.</li></ol>
0 commit comments