Do You Need an Agent Framework? Write the 40-Line Loop First
The agent loop is shorter than the quickstart for the framework that hides it. Write it once — then you can tell which framework features you actually need and which ones you'd be adopting on faith.
A first agent does not need a framework. It needs a loop, a list of tools, and somewhere to keep the messages. That comes to roughly forty lines.
The reason to write those forty lines yourself is not purity or minimalism. It is that every agent framework you might adopt later is a set of opinions about this same loop — where state lives, when to stop, what happens when a tool throws. You cannot judge opinions about code you have never written.
The whole loop
Take a concrete job: a morning on-call digest agent. It queries a few internal service endpoints, checks whether anything regressed overnight, and posts a short summary to a team channel.
Here it is, deliberately un-clever, as pseudocode:
TOOLS = [check_service, list_incidents, post_digest] # name + json schema + python fn
messages = [{"role": "user", "content": "Write this morning's on-call digest."}]
for step in range(MAX_STEPS):
reply = model.complete(
system=SYSTEM_PROMPT,
messages=messages,
tools=[t.schema for t in TOOLS],
)
messages.append({"role": "assistant", "content": reply.content})
calls = [b for b in reply.content if b.type == "tool_call"]
if not calls:
break # model answered instead of calling → done
results = []
for call in calls:
fn = lookup(TOOLS, call.name)
try:
out = fn(**call.arguments)
except Exception as e:
out = f"error: {e}" # hand the failure back; don't kill the run
results.append({"tool_call_id": call.id, "content": str(out)[:8000]})
messages.append({"role": "user", "content": results})
else:
raise StepBudgetExceeded(MAX_STEPS)
That is an agent. The model proposes a call, your code executes it, the result goes back into the transcript, and the loop runs again until the model stops asking. Everything else in the agent space is a variation on those five moves.
What the forty lines already buy you
More than newcomers expect:
- Multi-step tool use. The agent can check three services, notice one is degraded, pull its recent incidents, and only then write the digest — without you scripting that order.
- Error recovery. The
try/exceptturns a thrown exception into a message the model can read. Givenerror: service 'billing' not found, a decent model will retry with a corrected name rather than halting. - A stop condition that isn't vibes.
MAX_STEPSis a hard budget. The loop cannot bill you for a hundred turns because the model got stuck alternating between two tools. - A complete transcript.
messagesis your debugger. When the digest comes out wrong, the exact sequence of calls and results is sitting in a list you can print. - A truncation guard.
[:8000]stops one chatty endpoint from eating the whole context window.
Run this against a real API for an afternoon and you will understand the agent loop better than any diagram teaches it.
🔍 The four things you will write next
The loop above is honest but naive. Push it toward daily use and the same four gaps show up, in roughly this order:
- Persistence. The process dies at step 6 of 9. Right now the run is gone. You need the transcript in a durable store, keyed by run id, so a restart resumes instead of starting over.
- Parallel tool calls. The model asks to check four services at once and you execute them serially. Four sequential HTTP round trips is the difference between a two-second turn and a ten-second one.
- Observability. Printing
messagesworks for one run. It does not work for "which of last week's digests hallucinated an incident, and what did the tool actually return?" You need spans, timings, token counts, and searchable history. - Context compaction. Around step 15 the transcript is large enough to be expensive and slow. Something has to decide what gets summarized away and what stays verbatim.
Notice what is not on that list: prompt chaining DSLs, agent-graph builders, or an abstraction over model providers. Those are the features frameworks lead with. The four above are the ones you will actually reach for.
Where a framework earns it
Adopt one when the cost of building the item is higher than the cost of learning someone else's opinion about it. That is genuinely true for some things:
✅ Durable execution. Checkpoint-and-resume across process restarts is real distributed-systems work. If a library does it well, take it.
✅ Tracing that other people already read. Instrumentation is only worth it when the traces land somewhere your team already looks. Integration is most of the value.
✅ Multi-agent routing at scale. Once you have half a dozen specialist agents handing work between each other, hand-rolled dispatch turns into a mess of conditionals.
❌ Wrapping one model call in three layers. If a framework's main gift is renaming "call the model with tools" to something more elegant, it is adding a dependency and taking away legibility.
❌ Hiding the transcript. Any framework that makes it hard to see the exact messages sent to the model has removed your primary debugging tool. That cost is paid at the worst possible moment.
The uncomfortable pattern with heavy frameworks is not that they do too much. It is that the first hour is delightful and hour twenty is spent reading library source to find out why your tool result arrived reformatted.
💡 A decision checklist
Before adding an agent framework, answer these four:
- Have I run the raw loop against a real API? If not, stop. Everything below is unanswerable.
- Which of the four gaps am I actually hitting? Name them. "It seems more professional" is not one.
- Can I still print the exact request sent to the model? If the answer requires a debugger, the framework is too opaque for production.
- What is the exit cost? If the framework's abstractions leak into my tool definitions and business logic, migrating away means a rewrite. Prefer libraries you can call rather than frameworks you live inside.
Start narrow, adopt deliberately
The best sequence is boring: write the loop, ship something small that works, feel a specific pain, then adopt the narrowest thing that fixes that pain. A tracing library is a smaller commitment than an orchestration platform, and a durable queue you already run is smaller still.
Teams that start from the framework end up with a working demo and no mental model. Teams that start from the loop end up with a mental model and a demo that took a day longer. Only one of those groups can debug the thing at 3 a.m.