Building a coding agent from scratch: the harness is the product
A coding agent starts with a small tool loop. Making it dependable requires explicit tools, permissions, context control, verification, and a clear stop condition.
The short version The irreducible coding-agent loop is small: send messages and tool definitions to a model, execute requested tools, return the results, and continue until the model stops calling tools. The difficult work begins outside that loop: tool contracts, permission boundaries, context control, observable side effects, verification, and recovery. A useful first build is one model, a few narrow tools, a disposable repository, and an explicit completion test—not a multi-agent platform. The source repository was reviewed at commit f9e8b280f715f9ba107d4517fd39bc5f8ddda618; its current course uses runnable Python examples across 17 progressive stages.
A coding agent becomes easier to reason about when you stop treating it as a chat interface and start treating it as a runtime. The model proposes an action. The runtime decides whether that action is available and allowed, executes it, reports what happened, and gives the model another turn. Learn Claude Code makes that runtime visible by adding one mechanism at a time instead of hiding everything behind a framework.
The repository now has a canonical 17-stage track. It begins with one Bash tool and one loop, then adds file tools, permissions, hooks, planning, subagents, skills, compaction, memory, task graphs, background work, scheduling, teams, MCP, an integrated harness, workflow orchestration, and goal-controlled stopping. That progression is useful because it separates the stable kernel from the operating machinery that makes the kernel safe and useful.
The smallest useful loop
The first lesson implements the same round trip described in Anthropic's tool-use documentation. Your application sends conversation messages plus tool definitions. The model may return one or more tool-use blocks. Your code executes those requests, returns a matching tool-result block for each one, and asks the model what to do next.
This is enough to create agency in the product sense: the model can observe a result and choose another action. It is not enough to create a dependable coding product. The loop says nothing about which directory can be touched, whether two calls may run concurrently, what a failed command means, how long the run may continue, or what evidence proves the task is finished.
The loop enables action. The harness makes action governable. If the model is the decision-maker, the harness is the operating environment: tools, knowledge, observations, permissions, persistence, and the rules for stopping. Most production failures live in that environment, not in the six lines that call the model again.
Tools are contracts, not just functions
A tool exists twice. The model sees a name, description, and input schema. The runtime owns a handler that performs the action. Those two representations must agree. If the schema says a path is optional but the handler requires it, or the description hides a side effect, the model is reasoning against a false interface.
The repository's second stage replaces a single hard-coded Bash executor with explicit read, write, edit, and glob tools plus a dispatch map. The main loop does not need to know how each capability works. It only resolves a requested tool name to a handler. That is the right seam for adding validation, logging, timeouts, and tests without turning the control loop into a switchboard.
A tool contract should answer these questions before the model can call it Name and description What exact action is available? A vague tool is selected for the wrong job. Input schema Which values are required and constrained? Malformed or guessed arguments reach the handler. Permission policy Who may do this, where, and with whose approval? A valid call crosses a trust boundary. Result shape What observation comes back to the model? The next step is based on ambiguous output. Failure behavior What is retryable, terminal, or partially complete? A retry repeats a side effect.
Good error output is part of the contract. A coding agent cannot recover from a generic failure nearly as well as it can recover from a bounded explanation containing the exit status, relevant stderr, affected path, and whether anything changed. Observability is not a dashboard added later. It starts with what every tool returns.
Put permissions before capability
The tutorial deliberately exposes a risk in its first version: a Bash tool can execute a model-generated command. Its third stage adds a gate before execution—deny known forbidden operations, ask for context-dependent actions, and allow routine work. The chapter is also explicit that simple string matching is an illustration, not a complete security boundary.
Constrain location. Resolve every file path and reject anything outside the assigned workspace. A friendly prompt is not path isolation. Separate reading from mutation. Inspection can often proceed automatically. Writes, deletions, network changes, and external messages need narrower rules and sometimes approval. Make side effects legible. A request such as 'clean this up' should never silently expand into deletion, deployment, or communication with another person. Fail closed when nobody can approve. Background and asynchronous work should not invent consent because an interactive prompt is unavailable.
For a learning build, a disposable directory is the right starting environment. Give the agent a small fixture repository, no production credentials, no broad network authority, and no reason to touch the parent filesystem. You learn more from seeing a policy block a realistic action than from giving the demo unlimited power so it looks smooth.
Context is runtime state, not an infinite transcript
Every source file, search result, build log, model response, and tool result competes for the same finite context. Keeping everything eventually fails. Dropping history blindly creates a different failure: the agent repeats work, loses the active constraint, or separates a tool result from the call that produced it.
The repository's context-compaction lesson uses a useful order of operations. It persists unusually large results first, archives older messages, shortens consumed tool output, and asks the model to summarize only when deterministic reductions are not enough. It also preserves tool-call and tool-result pairs and carries the active request separately into the compacted state.
That ordering expresses a broader rule: remove the cheapest recoverable context before compressing meaning. A file can be reopened. A test can be rerun. A summary of why a constraint mattered is harder to reconstruct. Compaction should preserve the goal, current plan, decisions, modified files, failures, and verification status—not every byte the agent has seen.
Verification and stopping are part of the architecture
Anthropic's guidance on effective agents says agents need ground truth from the environment at each step, clear success criteria, feedback loops, stopping conditions, guardrails, and appropriate human oversight. For a coding agent, those ideas should become executable checks rather than optimistic prompt language.
Ground truth is the repository. Read the file, inspect the diff, run the targeted test, and check the generated artifact. The agent's explanation is not evidence that the change exists. Completion is a predicate. Define what must be true: requested behavior implemented, relevant checks green, no unrelated diff, and no unresolved failure hidden in the log. Retries have a budget. Repeatedly changing code after the same failure is drift, not persistence. Bound attempts and surface the blocker with the evidence already gathered. Side effects need identity. Before sending, deploying, deleting, or publishing, resolve the exact target and record whether the operation already happened so recovery cannot duplicate it.
The current repository eventually adds goal-controlled stopping, but the principle belongs in the first design. A model that stops producing tool calls has ended its turn. That does not prove the user's goal is complete. The harness needs its own definition of done and a way to return control when the evidence does not satisfy it.
Build one in six deliberate steps
Choose one bounded job. Use a fixture repository and a task with an objective result, such as locating a bug and preparing a patch that passes one named test. Do not begin with a general autonomous developer. Implement the message and tool loop. Start with one read-only tool. Record every model response, requested action, handler result, and stop reason so the full trajectory can be inspected. Add narrow file tools. Prefer explicit read, search, and patch operations over unrestricted shell for routine work. Validate paths and return structured, bounded output. Gate mutation. Require a visible diff and approval policy for writes. Keep deletion, credentials, deployment, and external communication out of the first version. Make verification callable. Expose the relevant formatter, test, and build checks. Return exit status and focused failure output, then make the completion rule depend on those results. Test failure before adding autonomy. Force a missing file, a denied path, a failed test, a long tool result, and an interrupted run. Add planning, memory, teams, or plugins only when the simple harness exposes a measured need.
What the exercise actually teaches
Building the small version does not teach you how to reproduce a mature coding product. It teaches you where the product boundary sits. Model quality matters, but so do the action space, the information returned after each action, the constraints around side effects, and the evidence required to stop.
That mental model changes how I evaluate coding agents. I look past whether a demo can generate a feature. I want to know whether the tool interfaces are precise, whether repository state remains recoverable, whether permissions survive ambiguous instructions, whether failures can be reconstructed, and whether the system can prove it finished the requested job without touching anything else.
The minimal loop is worth building because it removes the mystery. The production lesson is the opposite of minimalism: once a model can act, every boundary around that action becomes part of the product.
Harshith Vaddiparthy works with founders, operators, and teams on practical AI products, workflows, advisory, training, and mentorship. This no-JavaScript version preserves the page's core information and navigation.