How to Build a Coding Agent Harness From an Empty Loop A workshop at the AI Engineer Code Summit in San Francisco on 10 November 2026 will teach attendees to build a coding agent harness in TypeScript on Claude Haiku 5.5, starting from a bare `while (true)` loop around a single Anthropic SDK model call and adding one component at a time, each fixing an observed failure. The build proceeds through 9 git branches, adding a `read_file` tool and inner tool-use loop, then a 5-tool registry (`read_file`, `list_files`, `write_file`, `edit_file`, `bash`), then caps of 25 model calls and 300,000 tokens per task with a confirmation gate before irreversible actions. The author notes that the newest Claude models sign their thinking blocks, so message history must only be appended to, never edited, or the API returns an error. How to Build a Coding Agent Harness From an Empty Loop You build a coding agent harness by starting from a while true around one model call and adding one piece at a time, each piece fixing a failure you just watched happen. That’s the workshop I’m teaching at AI Engineer Code Summit https://www.ai.engineer/code/2026 in San Francisco on 10 November 2026: an empty loop at 9:00 and a harness you can serve over HTTP by 10:00, with 9 git branches so nobody in the room gets lost. This post is that hour written down. If you want the definition first, I wrote a whole post on what an agent harness is https://tej.as/blog/what-is-an-agent-harness after my talk Harnesses in AI https://tej.as/talks/ai-engineer-europe-2026 . That one drives GPT-3.5 Turbo https://platform.openai.com/docs/models/gpt-3.5-turbo through a browser until it stops lying. This one builds a coding agent, in TypeScript https://www.typescriptlang.org with the Anthropic SDK https://github.com/anthropics/anthropic-sdk-typescript as the only dependency, on Claude Haiku 5.5 https://platform.claude.com/docs/en/about-claude/models/overview because it’s cheap enough to run live in a workshop. Step 0: the model remembers nothing Here’s the first version in full. One array, one loop, one model call per line you type: js const messages: Anthropic.MessageParam = ; while true { const ask = await terminal.question "\nyou " ; messages.push { role: "user", content: ask } ; const response = await client.messages.create { model: MODEL, max tokens: 16000, messages } ; messages.push { role: "assistant", content: response.content } ; } Tell it your name, restart the process and ask again: it has no idea who you are. The model is a function that takes messages and returns one message that’s it . The array is the memory and the harness owns it. Look at the last line too: I push all of response.content and never just the text. The newest Claude models sign their thinking blocks and a history that gets edited instead of appended to comes back as an error. So the history only ever grows. Step 1: one tool and a loop make an agent Give it read file and a second while inside the first one: js while true { const response = await client.messages.create { model: MODEL, max tokens: 16000, tools, messages } ; messages.push { role: "assistant", content: response.content } ; if response.stop reason == "tool use" break; // run every tool use block, then send ONE user message with every tool result messages.push { role: "user", content: results } ; } That inner loop is the difference between a chatbot and an agent. The model never touches your disk. It asks to read a file and your code decides whether to. People trip up on the results here: every tool use needs exactly one tool result all of them in one user message and a tool that throws goes back as a result with is error: true . The model can recover from an error it can see. It can’t recover from one you swallowed. Step 2: the tool list is the domain Now the single tool becomes a registry: a definition the model reads and a function the harness runs, made by one small helper. I add list files , write file , edit file which refuses unless the text to replace appears exactly once and bash . Those 5 tools are what make it a coding agent. Swap them and you have a different agent with the same loop, which comes back in step 7. The descriptions matter more than people think because the model knows only what those strings say. They’re prompts. Ask it to “add hello.ts that prints today’s date, then run it” and it writes the file and runs it. This is where I expect the room to get a little nervous Step 3: a gate before anything you can’t undo Bash with no gate means “clean up” can mean rm -rf . So step 3 adds caps and a question for me: js const MAX STEPS = 25; // model calls per task const MAX TOKENS = 300 000; // input plus output tokens per task const ASK FIRST = new Set "write file", "edit file", "bash" ; Before any tool in ASK FIRST runs, the harness asks me y/N in the terminal. When I say no, the model gets an error result telling it to ask me what to do instead and it does . The caps make a stuck loop pause before it runs up a bill. The model proposes and the person decides. Step 4: every step resends the conversation This is the step that surprises people who’ve only used chat apps. Every call sends all of the history again: the system prompt, the tools and every tool result so far. The prompt is the bill. Claude Haiku 5.5 costs $0.10 per million input tokens, $0.01 per million cached and $0.50 per million out https://platform.claude.com/docs/en/about-claude/pricing . Past 100,000 tokens in one prompt, the price is 5× for every token in that request , which is insane … one tool result too many and your next call costs 5 times as much. So step 4 caches, cuts and compacts: 1. Caches the part that never changes, with one cache control field. 2. Cuts any tool result over 12,000 characters where it’s made, with a note telling the model how to ask for less. 3. Starts server-side compaction https://platform.claude.com/docs/en/build-with-claude/compaction-threshold at 90,000 tokens, before the price step and not after it. js const response = await client.beta.messages.create { model: MODEL, max tokens: 16000, system: SYSTEM, tools, messages, cache control: { type: "ephemeral" }, betas: "compact-2026-01-12" , context management: { edits: { type: "compact 20260112", trigger: { type: "input tokens", value: 90 000 } } }, } ; The harness also prints what every call costs. On a 3-call job the cached count goes from 0 to 1,040 tokens by the second call and the job costs a tenth of a cent in total. Step 5: phases beat a bigger model If each step works 99% of the time, a 122-step job run as one long loop finishes 0.99