What Happens When You Hit Enter: The Anatomy of a Coding Agent

How AI coding agents work: a practical guide to LLMs, tool calling, agent loops, 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 expands on that talk. It’s for software engineers who use AI coding agents but haven’t built an agent harness themselves. I’ll walk you through their surprisingly simple architecture, how context engineering makes them work well, and how to run them safely.

You can view the full slides or download them as a PDF.

The surprisingly simple architecture of an AI coding agent

The architecture of a coding agent is much less mysterious than it might seem. It comes down to three building blocks: an LLM, a set of tools, and a loop.

The LLM

The LLM is the heart of every coding agent. For now, it’s 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 doesn’t do. It doesn’t read files, run commands, or edit 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’s 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 agent loop

Tool calling becomes really powerful when we put it in a loop. Almost every agent loop looks something 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’s 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 engineering for coding agents

The basic components are simple, but building a good coding agent is hard. How well it works depends largely 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.

One model call · finite token budget
System prompt AGENTS.md Tool definitions Conversation Files + tool results Space left
Every instruction, file, and result competes for the same finite space.

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.

Implementations differ 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 don’t just 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.

That’s why good tools 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 doesn’t 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.

How to run coding agents safely

We’ve covered the simple core of an agent and the context engineering that makes it work well. The next question is how to run it safely. This matters because many local coding agents run with the same permissions as their user. With a shell tool, they can potentially do anything their user can do.

Remember that the LLM doesn’t 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:

Model LLM produces text
Your code Harness parses · decides · invokes
Machine Operating system files · shell · network
The model proposes an action. The harness decides what actually runs.

So control has to live in the harness. Luckily, we can design and constrain it ourselves.

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 don’t 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 isn’t 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 didn’t 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 never stands still. New programming languages, frameworks, patterns, and tools continually change how we work. AI is the latest tool in that progression, and it’s a very powerful one. If you’re not learning how to use it, you’re 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 isn’t the same thing. But I disagree with his conclusion.

For me, the fun didn’t disappear. It just shifted.

I’m 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?

They all come down to the same question: How can I automate more of my own work?

I find that genuinely fun. After all, automating things is why I got into 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. I still have more ideas than time, but more of them now make it past “someday.”

It’s an amazing time to be an engineer. There’s so much to build.