00. Introduction: An Agent Is Not a Single API Call
If you already know how to make a single large-model API call, what you have is "a function that answers questions." A coding agent solves a different class of problem: the user states a goal, and the system has to repeatedly observe the environment, call tools, interpret results, revise its plan, and save progress — and then keep working after the user interjects mid-run, stops it, or resumes it. It is not a longer prompt; it is a runtime built around the model, the agent harness that Harness Engineering is concerned with.
This course walks you through building a teaching project called tiny-agent. It does not replicate any existing product, but many of the core capabilities you see in coding agents like Claude Code and Codex rest on similar mechanics; this tutorial draws primarily on the key design decisions of Pi Agent-style real systems: the protocol layer is isolated from provider APIs, tool execution is driven by stop reasons, sessions treat an append-only log as the source of truth, long conversations advance through compacted context projections, file reads, writes, and command execution all pass through a security boundary, and the CLI, TUI, JSON mode, and SDK all consume the same event stream. Every design decision in this tutorial corresponds to a pothole a real system has already hit in production.
What you will end up building
The final project needs these capabilities:
- Accept a user task and call the model.
- Expose tools such as
read,grep,edit,write, andbash. - Execute tools in response to the model's tool use requests and feed the results back to the model.
- Emit an event stream of model text, tool calls, tool progress, tool results, and end-of-turn.
- Write all messages and non-message events to a JSONL session log.
- Restore context from any session, and compact when the context approaches the window limit.
- Support user steering or follow-ups while the model is running.
- Provide at least two shells: an interactive CLI and a machine-readable JSON mode.
- Use a harness to govern turn snapshots, mid-run changes, session-write ordering, and the settlement barrier.
- Test at zero cost with a faux provider and session replay.
This is not a toy chatbot. Each capability maps to a failure you will hit in a real product: the model generates invalid tool arguments, two parallel tools modify the same file at once, streaming output gets cut off, the context forgets a file it just read after compaction, the user changes the goal mid-run, the tool schema has already changed by the time a session is restored. This tutorial puts these failure modes front and center instead of handing you "best practices."
It is just as important to be clear about what this course does not build, so you can spend your attention on the core mechanics. It does not stand up a cloud service, it does not train or fine-tune a model, it does not integrate with any specific editor-plugin protocol, and it does not implement multi-agent collaboration or distributed scheduling. It assumes tools run on the local machine, leaving remote sandboxes and container isolation as a replaceable interface rather than a full implementation. What you are building is a single agent runtime that can reliably change code in a local repository; get that layer solid, and adding the cloud, an interface, or isolation later is a matter of swapping a boundary rather than rewriting the kernel.
Layering is the through-line of the whole course
An agent system that can evolve over the long term is usually split into five layers, each depending only on the contract of the layer below it:
- Protocol layer: unified message types, tool definitions, stop reasons, usage, and streaming events. Provider differences are isolated inside adapters.
- Agent loop: a state machine driven by model responses. It executes tools, feeds results back, and handles errors and aborts.
- Agent harness / runtime kernel: on top of the loop, it owns state, turn snapshots, queues, lifecycle, and event settlement.
- Product shells: interactive terminal, print mode, JSON mode, RPC, SDK. They are all different projections of the same event stream.
- Extension layer: lets third-party code register tools, commands, and hooks through a controlled API, rather than modifying the kernel.
This layering is not architecture on paper. It settles very concrete questions: what format of message the session log should store (protocol-layer types, not one vendor's response); which boundary tests should mock (the protocol layer, not the HTTP client); how much code a new JSON output mode takes to add (just serialization, without touching the kernel). Every chapter that follows fills in one piece of this diagram.
Map this layered diagram onto a single real request and you can see exactly which layer owns each step. User input first reaches the runtime kernel (layer 3), which builds the context and hands it to the protocol layer; the protocol layer's adapter (layer 1) converts the internal messages into some provider's request format, then converts the response back into an internal AssistantMessage with a normalized stop reason; the agent loop (layer 2) reads the stop reason and tool calls, executes tools, feeds tool results back, and decides whether to ask the model again; the product shell (layer 4) renders the events along the way into a terminal UI or JSON lines; and the extension layer (layer 5) inserts its own hooks before and after a tool call, before compaction, and at session start. One request, and each of the five layers does exactly one thing — that is precisely the determinism layering is meant to buy you.
Layer 3 is also where Harness Engineering primarily lives. The agent loop answers "how does this cycle advance?" Context Engineering answers "what should the next request see?" The harness answers "how are these capabilities assembled, when may they change, and when is the run truly finished?" Chapter 15 returns to those consistency contracts after all the underlying pieces exist and turns them into code and race-condition tests.
The reader contract
This course assumes you are comfortable with TypeScript, Node.js, Promises, async iterators, the command line, and basic file system APIs. Agent concepts are introduced from first principles: tool calling, loops, events, sessions, and tool boundaries. Example code stays at teaching scale — enough to express the boundaries, without cramming every edge case into an unreadable blob of code.
Every chapter has three layers:
- Concept: why this layer is needed.
- Structure: what the contract is between this layer and the ones above and below it.
- Checkpoint: what behavior you should observe once you finish.
If all you want is prompt tricks, this course is not for you. If you want to know how a system that can actually change code on your behalf is decomposed into protocol, loop, tools, state, permissions, interface, and extensions, that is exactly what this tutorial was written for.
Cost and testing strategy
Agent development cannot turn every test into a real model call. There are three reasons: the cost is uncontrollable, the output is not reproducible, and when something fails it is hard to tell whether the problem is the model or the runtime. So this tutorial introduces a faux provider from the very start. It is not a mock function that returns a string; it is scripted to return complete assistant messages, tool use, usage, and stop reasons. That lets you test the agent loop, tool error feedback, compaction, restoration, and UI events against recorded responses.
Real models are reserved for a small number of end-to-end checks. The default development workflow should be:
- Unit tests use the faux provider.
- Integration tests use session replay.
- A handful of smoke tests call the real model.
- Cost accounting goes into the usage field of every assistant message.
Cost accounting is worth getting right from day one. In a mature system, usage is not just input and output tokens; it also includes cache reads, cache writes, and reasoning tokens, converted into money using the per-unit prices in the model catalog. If you wait until a user asks "why did this task cost two dollars" to add it, you will have to backfill every historical message. These five kinds of token — input, output, cache read, cache write, and reasoning — should be recorded in separate fields from the start; they are the facts that both the provider protocol in Chapter 4 and the cost accounting in Chapter 16 depend on.
This discipline runs through the entire course. An agent without reproducible tests quickly turns into a black box tuned by feel.
The core mental model
You can think of a coding agent as the following data flow:
user goal
-> context builder
-> provider adapter
-> model response
-> stop reason
-> tool executor
-> tool result
-> session log
-> next context projection
The most important separation here is between the "log" and the "context." The log is the source of truth: it records what happened. The context is a projection: it is only the slice currently being prepared for the model. Compaction, branching, restoration, UI rendering, and extension records should all be built on the log — not the other way around, treating the current prompt as the system state. Deciding which slice of the log to project into the context for each request is exactly the question Context Engineering answers, and Chapter 9 develops it in depth.
The direct consequence of this separation in a real system is that a session can be a tree rather than an array (the user can fork and retry from any historical node); compaction deletes no history, it only changes the projection rules; and the same log can simultaneously drive terminal rendering, HTML export, and test assertions. This tutorial lands all of it in code in the chapters on the session log and compaction.
Chapter checkpoint
After reading this chapter, you should be able to state the difference between an agent and a single LLM API call in one sentence: an agent is a recoverable runtime built around model responses. The hard part is not "getting the model to say something," but "when the model wants to act, how the system reliably executes, records, feeds back, and continues."