How an AI Coding Agent Works: The Read, Edit, Run Loop

A
Author
18 Oct, 2026 4 min read 0 views

Every AI coding agent, whatever its brand, is a loop. The model reads the conversation so far, decides whether it needs a tool, and either asks for one or writes a final answer. The program around the model runs the requested tool, appends the result to the conversation, and calls the model again. Understanding this loop explains most of the behaviour you see: why agents sometimes wander, why they forget things, and why permissions exist.

The loop in five steps

  1. Prompt. The program builds a request: system instructions, your project instruction file, the conversation, and a list of available tools with their descriptions.
  2. Model response. The model returns text, a tool request, or both. A tool request names the tool and gives arguments, for example "read file src/app.js".
  3. Permission check. The program decides whether to run the tool, ask you, or refuse.
  4. Execution. The tool runs and produces output: file contents, search hits, test results or an error.
  5. Observation. The output is added to the conversation and the loop returns to step 2.

The loop ends when the model replies with no tool request. That final text is the summary you read. Nothing else runs between steps; the model has no hidden background activity. It only "knows" what is in the conversation.

A minimal sketch

The code below shows the control flow in plain Python. It is not tied to any vendor. The model function stands for a call to a language model API.

def run_agent(task, model, tools, max_steps=20):
    messages = [{"role": "user", "content": task}]
    for _ in range(max_steps):
        reply = model(messages, tools)          # step 2
        messages.append(reply.as_message())
        if not reply.tool_calls:
            return reply.text                   # loop ends
        for call in reply.tool_calls:
            if not allowed(call):               # step 3
                result = "Denied by user"
            else:
                result = tools[call.name](**call.args)  # step 4
            messages.append({"role": "tool",
                             "id": call.id,
                             "content": str(result)})   # step 5
    return "Stopped: step limit reached"

Two details matter. First, max_steps puts a ceiling on the loop so a confused agent cannot run forever. Second, every tool result is appended to messages, so the conversation grows with each step. A long task with big outputs fills the model's context window, a limit we cover in a later post.

Why this explains common behaviour

Agents explore before they edit

The model cannot see your repository. To change code it first has to read it, so early steps are usually searches and file reads. If your repo is well organised and your instruction file says where things live, the agent needs fewer steps.

Agents verify through tools, not by thinking

A model cannot tell whether its code compiles by looking at it. It can run the compiler or the tests and read the output. This is why a fast, reliable test command is the most valuable thing you can give an agent. Without feedback it can only guess.

Agents drift when the conversation gets long

Each step re-sends the whole conversation. Old instructions get diluted by thousands of lines of tool output. Tools often summarise or trim old content, but details can still be lost. Starting a fresh session for a new task is cheap and often the right call.

Permissions sit between the model and your machine

Step 3 is where safety lives. The model can ask for anything, including a destructive command. The surrounding program decides what runs. Later posts cover how to tune this so routine commands run without prompts while risky ones always ask.

Tools are just descriptions plus code

The model learns about a tool from a name, a description and a schema of arguments. A simple file-reading tool might be declared like this:

{
  "name": "read_file",
  "description": "Read a UTF-8 text file from the project and return its contents.",
  "input_schema": {
    "type": "object",
    "properties": {
      "path": { "type": "string", "description": "Path relative to the project root" }
    },
    "required": ["path"]
  }
}

The model never executes this. It emits a structured request like {"name": "read_file", "input": {"path": "src/app.js"}}, and your program does the work. This is also how extra capabilities are added: connect a new tool and describe it well, and the model can use it.

What to take away

  • Quality of results depends on what ends up in the conversation: instructions, files read and tool output.
  • Fast feedback tools (tests, type checks, linters) let the agent correct itself.
  • Permissions are enforced by the program, not by the model's good manners.
  • Long sessions degrade; short, focused sessions work better.
  • When an agent misbehaves, ask what it could see at that step. The answer is usually the cause.
Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.