What Happens When You Hit Enter: The Anatomy of a Coding Agent
A practical look inside coding agents: the LLM, tools, agent loop, context engineering, approvals, and isolation.
At techcamp Hamburg 2026, I gave a talk titled “What Happens When You Hit Enter: The Anatomy of a Coding Agent.” My goal was to explain what happens behind the scenes when a coding agent turns a prompt into a pull request.
This article is an expanded version of that talk. It is written for software engineers who use coding agents but have never built an agent harness themselves. We’ll start with the surprisingly small core of an agent, then look at context engineering and, finally, how to run an agent safely.
You can view the full slides or download them as a PDF.
The surprisingly small core of a coding agent
The basic architecture of a coding agent is much less mysterious than it might seem. At its core, it consists of just three building blocks: an LLM, a set of tools, and a loop.
The LLM
The LLM is the heart of every coding agent. For a basic understanding, it is fine to treat it as a black box: it receives input and generates text. The input is usually text, although modern models can also accept images, audio, or video.
The important part is what the LLM does not do. It does not read a file, run a command, or change your code. It only generates text. Even when the model says it wants to execute something, another piece of software still has to make that happen.
Tool calling
When an agent does something for you, it happens through a tool call. The first thing the model needs is a tool definition. This tells the model that a tool exists, what it does, and how to call it. It looks similar to a function signature in code: every tool has a name, a description, and a set of typed input parameters.
Here is a simplified definition for a tool that reads a file:
{ "name": "read_file", "description": "Read a file from the repository", "parameters": { "type": "object", "properties": { "path": { "type": "string" } }, "required": ["path"] }}The harness passes this definition to the LLM. The model can then either answer the user directly or generate a structured tool call:
{ "name": "read_file", "arguments": { "path": "src/auth.ts" }}This tool call is still only model output. The harness parses it, validates the arguments, executes the corresponding function, and returns the result to the model. In other words, the model asks for an action; the harness performs it.
The loop
Tool calling becomes powerful once we put it in a loop. At a high level, nearly every agent loop looks like this:
messages = [systemPrompt, userPrompt]while true: response = llm(messages, tools) messages.append(response) if response has no tool calls: break for each call in response.toolCalls: messages.append(execute(call))The messages array represents the conversation so far. Initially, it contains the system prompt and the user’s request. The harness sends those messages and the tool definitions to the LLM, then appends the response to the conversation.
What happens next depends on that response. If it contains a normal text answer, the harness shows it to the user and stops. If it contains one or more tool calls, the harness executes them, adds their results to the conversation, and calls the LLM again. This continues until the model returns a final answer or the harness stops the loop.
Put these pieces together and you have an agent:
An agent is an LLM with tools that runs in a loop.
Nothing about that loop makes it a coding agent yet. The tools do. Almost every coding agent exposes the same core capabilities, even if the tool names and boundaries differ: reading files, modifying files, and running commands. A common minimal set divides those capabilities into four operations:
read(path)reads a file and returns its contents.write(path, content)creates or overwrites a file.edit(path, oldText, newText)replaces a specific piece of text in a file.bash(command)runs a shell command and returns its output.
Together, these tools let an agent inspect a codebase, make changes, search for references, run tests, and use the same command-line tools as a developer.
Pi is a good real-world example. It is an open-source coding agent deliberately designed to stay small. Its basic setup provides these four tools and a short system prompt; most additional orchestration is optional. If you want to build your own coding agent without starting from zero, Pi is a useful foundation.
Context
The core components are simple. Building a coding agent that works well is not. In practice, much of its quality depends on whether the model has the right context at the right time.
Context is everything the model can see during its current call. That includes the system prompt, tool definitions, the conversation, file contents, and previous tool results. An LLM has no memory between calls, so the harness has to assemble and send this context again for every turn.
Persistent context and context on demand
The user’s message is the most immediate source of context, but an agent often needs additional instructions about the project it is working in. Files such as AGENTS.md can provide persistent project context: conventions, useful commands, architectural notes, and boundaries the agent should respect.
Skills solve a different problem. A skill packages instructions, references, and sometimes helper scripts for a particular kind of task. The agent can be made aware that a skill exists through a small name and description, then load the full instructions only when they are relevant. Instead of putting every possible procedure into the base prompt, skills provide context on demand.
The exact implementation differs between agents, but the principle is the same: keep essential project rules available and load specialized knowledge only when the task needs it.
Context is a budget
All of this context has to fit into a finite context window. Every instruction, file, and command output competes for the same space. This makes context a budget that the harness has to manage deliberately.
Protecting context at the tool boundary
Tools do not only perform actions. Their results also become part of the next model call. Reading a huge file or returning thousands of lines of command output can quickly crowd useful information out of the context window.
Good tools therefore limit what they return. Pi’s read tool, for example, returns at most 2,000 lines or 50 KB at once. The agent can request another section with offset and limit when necessary. A shell tool should apply similar protection to large command outputs.
Another common strategy is file offloading. The harness saves the complete output to a file and returns only a useful excerpt together with the file path. The agent can then search the full output and read the relevant parts instead of carrying everything through every subsequent turn.
This is why tool design is also context engineering. A careless cat can do more damage to an agent’s context than a mediocre system prompt.
Truncation and compaction
Sooner or later, a long-running agent approaches its context limit. To continue working, the harness has to make room. Two common strategies are truncation and compaction.
Truncation removes older or less useful content. Tool results are often good candidates because they take up a lot of space and can frequently be retrieved again. Instead of deleting them completely, the harness can also replace them with a reference to an offloaded file.
Compaction is more deliberate. The harness asks an LLM to summarize the conversation, replaces older turns with that summary, and usually keeps the most recent turns unchanged. This frees space without discarding the entire history.
The trade-off is simple: more room, less detail. A useful compaction summary should preserve the goal, important decisions, constraints, changes already made, and approaches that failed. If a failed approach disappears from the summary, the agent may happily try it again.
Sub-agents as context engineering
Another useful pattern is to give a bounded task to a sub-agent. The sub-agent works with its own context window and returns a concise result to the main agent when it is finished. The file reads, logs, experiments, and dead ends involved in that task can stay out of the main conversation.
Adding more agents does not automatically make a system smarter. The main advantages are separate context windows and parallelism, which can reduce latency when tasks are genuinely independent. The costs are additional coordination and usually higher token consumption.
I find it most useful to think of sub-agents as a context-engineering technique. They are especially valuable for noisy tasks such as exploring an unfamiliar part of a repository, investigating logs, or comparing several independent approaches. Only the useful result needs to return to the main agent.
What else belongs to this topic?
Several related topics only received a brief mention in my talk:
- Memory: What should survive between sessions, and how should it be retrieved later?
- To-dos and task state: How can an agent externalize progress instead of relying on conversation history to stay on track?
- Context ordering and prompt caching: Stable prompt prefixes can reduce cost and latency, while unnecessary reordering can reduce cache hits.
- MCP and tool discovery: When should tools and their schemas enter the context, especially when users can connect large tool catalogs?
- Retrieval: How can an agent load only the relevant code, documentation, or history without introducing more noise?
Each of these could be an article of its own. The main lesson is that the loop is simple; deciding what the model should see is the hard part.
Safety
So far, we have covered the simple core of an agent and the context engineering that makes it work well. The final question is how to run it safely. This matters because many local coding agents run with the same permissions as their user. Through a shell tool, they could potentially do anything the user could do.
Remember that the LLM does not execute an action itself. It generates a tool call as structured text. The harness parses that call and turns it into an actual system operation:
Control therefore has to live in the harness. This is good news because the harness is software we can design and constrain.
Approvals
One common safety layer is an approval policy between the generated tool call and its execution. For every request, the policy decides whether to allow it, ask for confirmation, or deny it.
A human-in-the-loop policy asks the user before an action runs. Allowlists and deny rules can make obvious decisions automatically, such as always allowing read while asking before write. An auto mode can use a second LLM to classify a requested action as safe or unsafe. More advanced policies can also consider the command, file path, network target, or credentials involved.
Approvals reduce risk, but they do not eliminate it. Automated classifiers can be wrong, and humans can approve something dangerous by mistake.
Isolation
Isolation adds another layer of protection. It means running the agent harness inside a constrained environment such as a sandbox, container, or micro-VM. That environment can restrict which files, credentials, processes, and network destinations the agent can access.
Approvals answer the question, “Should this action run?” Isolation answers, “What can it reach if it does?” The two approaches complement each other. Even when a bad action is approved, isolation can limit the damage it causes.
Putting it all together
The core building blocks of a coding agent are an LLM, tools, and a loop. Context engineering determines what the model sees and helps the agent remain effective during longer tasks. Approvals and isolation add control around what the agent is allowed to do and what it can reach.
This architecture is not unique to coding agents. In practice, many coding agents are already capable general-purpose agents. The same core tools we described, file access and shell execution, can also be used to analyze data, transform documents, automate workflows, and interact with other software through command-line interfaces.
Codex is a good example. It started as a coding agent, but its current use cases extend far beyond writing code. The core architecture did not need to change. Its scope expanded through context, skills, and integrations.
I ended my talk with two more personal questions.
Should you use AI for coding?
My answer is yes, definitely.
Software engineering has always moved. New programming languages, frameworks, patterns, and tools have continually changed how we work. AI is the latest tool in that progression, and it is a very powerful one. If you are not learning how to use it, you are falling behind.
Is this still fun?
An engineer once told me that he hated AI because it had robbed him of the fun of coding. I understand where that feeling comes from. Writing code by hand is fun, and writing prompts is not a direct replacement for it. But I disagree with his conclusion.
For me, the fun did not disappear. It shifted.
I am constantly thinking about how to improve my AI coding setup. Which harness should I use? Which model fits the task? Which other systems could I connect to my coding agent? Am I repeating the same instructions often enough that they should become a skill? Could this manual process become a pipeline or workflow?
The question behind all of these is always the same: How can I automate more of my own work?
I find that genuinely fun. In a broader sense, automating things is why I started coding in the first place. Designing the setup around an agent feels like another form of engineering, not a departure from it.
AI also helps me turn far more ideas into working software than before: a website, an app, a game, a better development setup, or a custom CLI tool. I still have more ideas than I can realize, but more of them now make it past “someday.”
In my opinion, it is an amazing time to be an engineer. There is so much to build.