# An AI Agent Is Just a Loop and a Capability Table

> Strip the frameworks away and an LLM agent is about 35 lines: call the model, if it asks for a tool run the tool and feed the result back, repeat until it stops. I built one from scratch to prove it — and the systems concepts underneath are all familiar.

URL: https://harshith.in/blog/building-an-agent-loop/
Author: Harshith Sunku (Staff Engineer) — https://harshith.in/about/
Tags: ai, systems

The word "agent" collected a lot of mystique, so I did the thing that
usually dissolves mystique: [built one from scratch](https://harshith.in/projects/build-your-first-ai-agent/), no
framework, and watched what was actually required. The answer is
almost anticlimactic. An agent is a **loop plus a capability table** —
about 35 lines of control flow around a model that can only talk. Every
"agentic" system, however elaborate, is this core with a bigger tool
list. Here's the whole thing, and why each piece maps to a systems
idea you already know.

## The Loop

The model itself does nothing but produce text (or a structured
request). It can't read a file or call an API. What makes it an *agent*
is a loop that lets it *request* actions and feeds back results:

```mermaid
flowchart TD
    U[user question + system prompt] --> M[call the model]
    M --> D{model output}
    D -- tool request --> T[your code runs the tool]
    T --> F[append result to transcript]
    F --> M
    D -- final answer --> E[return to user, stop]
```

In code, minus error handling:

```python
messages = [system_prompt, user_message]
for _ in range(MAX_ITERATIONS):
    resp = model(messages, tools=TOOL_SCHEMAS)
    if resp.tool_calls:
        for call in resp.tool_calls:
            result = TOOLS[call.name](**call.args)   # YOUR code executes
            messages.append(tool_result(call.id, result))
    else:
        return resp.content                          # model chose to stop
```

That's it. The profound-sounding parts demystify one by one:

- **The model is a control plane, not an executor.** It only ever
  *names* a tool and supplies JSON arguments. Your code decides
  whether and how to run it. The model talks; your loop acts. Every
  security property flows from this: the model never touches your
  system directly, so the trust boundary is your tool implementations,
  and giving an agent a `run_shell` tool is precisely as dangerous as
  it sounds because *now* it can act.
- **Tools are a capability table.** Swap the table, keep the loop, and
  the same agent explores a codebase, triages logs, or queries a
  database — I demonstrated exactly this: identical control loop,
  three different read-only tool sets, three different agents. The
  loop is generic; the *capabilities* are the product.
- **`MAX_ITERATIONS` is a TTL.** It's the hop count that stops a
  runaway loop — the model can keep requesting tools forever
  (retrying a failing call, chasing its tail), and the iteration cap
  is the same defensive bound as a packet TTL or a retry limit. First
  thing you add after the naive version.
- **The transcript is the entire state.** The model is stateless
  between calls (see my inference post); "memory" is just the growing
  `messages` list re-sent each iteration. Which immediately surfaces
  the real engineering constraint below.

## Where the 35 Lines Meet Reality

The loop is trivial; making it robust is where actual systems work
lives, and it's all recognizable:

- **Context is a resource budget.** The transcript grows every
  iteration and the context window is finite, so long agent runs
  force the same decisions as any bounded buffer: summarize old turns,
  evict tool outputs you no longer need, page detail in and out. A
  20-tool-call debugging session overflows naive context; managing it
  is most of what "agent memory" means.
- **Tools fail, and the model must hear about it.** Feed errors back
  as tool results (`"error: file not found"`) rather than crashing —
  the model can often recover, retry differently, or report honestly.
  Your tool layer is an API boundary with an untrusted, occasionally
  confused caller: validate arguments, sandbox side effects, make
  destructive tools require confirmation, and never assume the
  arguments are well-formed.
- **Determinism is gone.** The same input can take different tool
  paths on different runs. Testing shifts from asserting exact outputs
  to evaluating behavior over a fixed task set (LLM-as-judge, success
  metrics) — closer to testing a flaky distributed system than a pure
  function.
- **Multi-agent is just tools that are agents.** A "supervisor"
  orchestrating specialists is the same loop where some tools happen
  to invoke *other* loops. Delegation is a tool call; there's no new
  primitive, just composition — which is the honest antidote to a lot
  of multi-agent-framework marketing.

The reason I find this clarifying rather than deflating: it means
agents are amenable to ordinary engineering. The loop is a state
machine, tools are an API surface with a hostile caller, context is a
cache-eviction problem, iteration caps are TTLs, and orchestration is
composition. Frameworks (LangChain and friends) package retries,
parsing, and memory so you don't rewrite them — genuinely useful — but
they're conveniences over these 35 lines, not magic you couldn't write.
Knowing that changes how you debug: when an agent misbehaves, you read
the transcript and the tool results, because that's the whole state.

## Takeaways

- An agent = a loop that calls the model, executes any tool the model
  requests, feeds results back, and stops when the model stops —
  ~35 lines.
- The model is a control plane that only *names* actions; your code
  executes them, so the trust boundary and all real capability live
  in your tools.
- Swap the capability table to get a different agent; MAX_ITERATIONS
  is a TTL; the transcript is the entire state.
- The hard parts are ordinary systems work: context as a resource
  budget, tools as a hostile-caller API, non-determinism forcing
  eval-based testing.
- Multi-agent is tools-that-are-agents — composition, not a new
  primitive; frameworks package the plumbing, not the magic.
