A model can only read text and write text. It can't open your files, run your tests, or remember yesterday. The code around the model does all of that. That code is the harness.
This repository builds a small but real coding agent, one idea at a time. Each step is a commit, and each step has a chapter that explains the idea, shows the code, and gives you something to run.
By the end you will have written, by hand, the parts that make tools like Claude Code work:
- an agent loop that calls tools and reads the results
- tools for reading, searching, editing, and running commands
- error handling the model can act on
- a system prompt built from parts, plus project instructions and skills
- a context budget, and a plan that survives when old results are cleared
- permissions, a sandbox, dry runs, and hooks
- a verification loop that checks the agent's work before it finishes
- subagents, memory, and tracing with cost reports
- Node 26. It runs
.tsfiles directly, so there is no build step. Run files withnode lessonN.ts. - A model. The default uses the Claude Code CLI (
claude -p). Install Claude Code and log in. You don't need an API key for that setup. - git. Each lesson is a commit, so you can compare and reset.
- macOS for lesson 14. The sandbox uses
sandbox-exec, which only exists on macOS. On other systems, bash is refused on purpose. Read chapter 14 before you try to work around that.
Every model call goes through one function, callModelDetailed in model.ts. It takes a prompt and a system prompt, and returns:
{ text, inputTokens, outputTokens, costUsd, ms }To use another model, replace the body of that function. Keep the return shape. OpenAI, Anthropic's API, Groq, Ollama, or a local model all work the same way: send the prompt, read back the text and the token counts. Nothing else in the repository calls a model directly.
Two things to know:
- Lesson checks that call a model will vary from run to run, because models are random. The chapters say when this happens.
- Cost is reported from what the CLI returns. If your model returns no price, cost shows as 0. Compare runs by tokens instead.
- Clone the repository.
- Open the chapter for the lesson you want.
- Check out that chapter's commit (
git checkout <commit>), read the chapter, and run the commands in it. - Make the change yourself before you look at the next commit. Then compare with the commit's diff.
- When you're done, return with
git checkout main. Checking out a commit leaves you on a detached HEAD, and your changes there are easy to lose.
One exception: lessons 1 to 6 share one commit, 51194ce. Read those chapters in order and use the file each chapter names. Every later lesson has its own commit.
| # | Chapter | Commit | You learn |
|---|---|---|---|
| 1 | What a harness is | 51194ce |
Why the model needs a harness |
| 2 | The agent loop | 51194ce |
Ask, act, feed the result back |
| 3 | Reading and searching | 51194ce |
Tools that look at the project |
| 4 | Editing files | 51194ce |
Edits that can't silently break things |
| 5 | Tool errors the model can act on | 51194ce |
Errors as instructions, bad replies as retries |
| 6 | Running commands | 51194ce |
Shell access with a timeout and an exit code |
| 7 | Shaping results | 5b658b3 |
Keep the start and the end, cut the middle |
| 8 | The system prompt | 0b79428 |
Build the prompt from the tool registry |
| 9 | Project files | 290c0c8 |
Instructions that live in the repo |
| 10 | Loading what is needed | e309afe |
Skills: a short index, full text on demand |
| 11 | The context window | e8ddf04 |
Budgets, and what clearing costs |
| 12 | Plans and todos | 5d23884 |
A plan that survives clearing |
| 13 | Permissions | 47e7bc5 |
Allow, ask, deny, before every call |
| 14 | Sandbox and dry runs | a637c73 |
The OS limits what a command can do |
| 15 | Hooks | c05fca7 |
Your scripts run at fixed points |
| 16 | Verification loops | 8f70148 |
Don't believe "done" without a check |
| 17 | Subagents | af4c5a3 |
Hand a reading task to a fresh agent |
| 18 | Memory | 3f343c0 |
Notes that survive the session |
| 19 | Tracing and cost | 62189e2 |
Measure where time and tokens go |
lessonN.ts: the runner for lesson N. It shows the idea in action.model.ts: the only place that calls a model.agent.ts: the loop that asks the model, runs tools, and checks for an answer.tools.ts: the tool registry (ls,grep,read,edit,write,bash, and latertodo,remember,delegate, ...).prompt.ts: builds the system prompt from parts.permissions.ts,sandbox.ts,hooks.ts: what the agent may do, and how it is stopped.plan.ts,window.ts,memory.ts,subagent.ts,trace.ts,report.ts: the rest of the machinery.hooks/,skills/,playground/: the examples the lessons use.
Some files are demo data that lessons change on purpose (notes.txt, secret.txt). Restore them with git checkout -- <file> before you re-run a lesson.
This agent runs commands and edits files. Run it in a throwaway folder or a repository you can reset. The lessons ask for a yes before risky actions, and some runs deliberately try things the rules should refuse. Keep an eye on what it does.