How Does a Coding Agent Actually Work? Build a Minimal One
A coding agent is a loop around a model with tool calling. Send the goal, receive text or a tool call, execute the tool, append the result to the conversation, repeat until the model stops calling tools. Four tools cover most work: read, edit, search, and run a command. Everything else is context management and permissioning.
The loop
Strip everything away and a coding agent is this:
messages = [system_prompt, user_goal]
loop:
response = model(messages, tools=TOOLS)
if response has no tool call:
print(response.text); break
for call in response.tool_calls:
result = execute(call) # after permission check
messages.append(call)
messages.append(result)
That is the whole control flow. There is no planner, because the plan is produced by the same generation that produces the tool call, which is why it stays consistent with reality. There is no state machine, because the conversation is the state.
Two details matter more than they look. First, tool results go back into the conversation verbatim, including errors. A failed command with its stderr is the single most useful thing you can hand a model, and agents that swallow errors into a friendly message lose most of their debugging ability. Second, the loop needs a stop condition beyond the model deciding to finish: a turn cap or a token cap, so a confused agent fails visibly instead of spinning.
The four tools
read(path, offset, limit). Returns file contents with line numbers. Line numbers matter because they give the model a stable way to refer to a location in the next edit call.
edit(path, old_string, new_string). String replacement, not line based patching. Line numbers drift the moment anything else changes, and a diff format the model half remembers produces corrupt files. Requiring an exact unique match of the old text makes a wrong edit fail loudly instead of applying in the wrong place. Reject the call if the old string appears zero times or more than once.
search(pattern, path). Grep, not embeddings, at this layer. The model wants literal matches for symbol names, and it wants them fast.
run(command). A shell. This is where most of the long tail lives: tests, build, git, package managers, whatever the project already uses. It is also the tool that makes permissioning non optional.
Add a fifth or sixth only when composition genuinely cannot express the operation. Every registered tool costs schema tokens on every request, forever.
Why the edit tool deserves the most care
Read failures are visible. Search failures are visible. A bad edit is not. It writes plausible looking code into the wrong location and the agent proceeds confidently on a corrupted file. Enforce exact match, enforce uniqueness, and re-read the file after writing when you can afford it. This one tool is responsible for most of the difference between an agent that feels reliable and one that feels dangerous.
Permissioning: the part you cannot delegate
The model decides what to do. A human decides what is allowed. Those are different layers and the second one has to live in code.
Put the check between the tool call and its execution. Classify by side effect: reads are usually safe to auto approve, edits inside the working tree are a middle tier, and anything that touches the network, the package manager, or paths outside the project should prompt. Allow a session level approval for repeated commands so the workflow stays usable, and never let an approval carry across a change in scope.
A rule that only exists as a sentence in the system prompt is not a control. It is a suggestion the model can reason its way past under pressure, and it will, usually while trying hard to be helpful.
Context assembly is the real work
Once the loop runs, every remaining quality decision is about what goes into the window each turn.
A naive agent appends everything and grows until it hits the limit, at which point behavior degrades sharply. A working agent manages the window deliberately: keep the goal and recent turns intact, summarize or drop old tool output that has been superseded, and never carry the full contents of a file that has since been edited.
The deeper problem is what the agent has to learn before it can do anything. Point a fresh agent at an unfamiliar repository and its first ten tool calls are orientation: find the entry point, understand the module layout, learn the test command, infer the conventions. That work is identical every session and you pay generation prices for it every time.
This is where retrieval changes the architecture rather than just optimizing it. Index the repository and docs once as searchable chunks, and the agent asks a question and gets the two relevant passages instead of reading nine files to reconstruct the answer. RDK does this with encrypted private chunks: the agent searches your indexed material before it queries a model, and token spend drops 80 to 90 percent because the answer is retrieved instead of regenerated.
Where the tokens actually go
In a homemade agent the bill breaks down into three buckets: the system prompt and tool schemas resent on every request, the accumulated tool output in the conversation, and the model's own generated reasoning. The first two usually dominate, and both are addressable without touching the model. Trim the schemas, prune superseded tool output, and move standing knowledge into retrieval.
What to build after it works
Once the loop, the tools, and the permission gate are in place, the useful additions in rough order of payoff are: streaming output so the user can interrupt, a way to interrupt mid tool call, a persistent per project instruction file, tool result truncation with a way to fetch more, and a retrieval call the model can make before it starts reading files.
The additions that usually disappoint are the ones that add cognitive machinery: task decomposers, planning phases, and critique loops. They were valuable when models planned poorly. They now mostly add latency and a second place for the agent's understanding to diverge from the code in front of it. Build the thin version first and add machinery only where you can measure the gain.
Frequently asked questions
- How much code does a working coding agent take?
- The loop is under a hundred lines and the four core tools are a few hundred more. Most of the remaining effort goes into edit tool safety, permission handling, and context management, none of which is algorithmically hard but all of which decides whether the agent is usable. Complexity beyond that is usually optional.
- Should the edit tool use diffs or string replacement?
- String replacement with an exact unique match. Line numbers drift as soon as anything else in the file changes, and models produce subtly malformed diffs that apply in the wrong place. Requiring the old text to appear exactly once makes a wrong edit fail loudly rather than silently corrupting a file.
- Does a homemade agent need a planner?
- Usually not. The model produces a plan as part of the same generation that produces the tool call, so the plan updates automatically when the environment disagrees with it. A separate planning stage adds latency and creates a second version of the truth that drifts from the code. Add one only if you can measure an improvement.
- Why is my agent so expensive to run?
- Almost always rediscovery and accumulated context. The agent re-reads the same files each session to rebuild the same understanding, and old tool output stays in the window long after it is superseded. Prune the conversation, trim tool schemas, and move standing project knowledge into a retrieval index the agent can query.