An AI Agent is a model given a goal, tools, and permission to keep going. Here is the loop, the five parts, the four ways it stops, and a working implementation in about 40 lines.
Companion resource for Day 01 of 30 Days of AI Agents.
The carousel answered the question "what is an agent?" This post answers the one that always comes next, which is whether the thing you're building is one, whether it should be, and what it needs to work.
None of this expires when a library changes its API. It's a mental model rather than a framework tutorial, so it should still be useful to you in a year.
If you'd rather have the practical version first, skip to the 8-question self-test at the bottom and come back for the reasoning.

Every agent runs the same four moves in a cycle. Here's what actually happens at each one, including the parts that usually get skipped.
The goal is a description of the outcome, written by you, and it's the only part of the system a human always writes by hand. Everything downstream inherits its precision, which is why sloppy goals produce sloppy agents no matter how good the model is.
What you want out of it is a success condition the loop can eventually test against. "Improve the onboarding docs" has no stopping point, so an agent given that goal will either wander or stop arbitrarily. "Every page in /docs/onboarding has a working code sample that runs against the current SDK" gives the loop something to check itself against, and it tells you what failure looks like too.
Into this step goes the goal plus every observation gathered so far. Out of it comes a decision about the next single step.
The word "single" is doing a lot of work there. The model isn't producing a full plan, because it can't: it doesn't yet know what the next observation will contain. This trips people up, because asking an LLM to produce a plan feels like the natural thing to do, and plenty of tutorials tell you to. But if you generate a five-step plan upfront and then execute it blindly, you've thrown away the only real advantage the loop gives you, which is that step three can change based on what step two returned.
The chosen step goes in, and a tool call comes out: a real function invocation with real arguments.
This is the boundary between talking and doing, and it's the only stage where something outside the conversation changes. It's also where tool design starts to matter. The model picks tools by reading their descriptions, so a tool documented as "gets data" will get called at the wrong times, and you'll spend an afternoon debugging the prompt when the problem was the docstring.
The raw result of the tool call comes back, including failures, and it becomes a fact that carries into the next Think step.
The mistake here is swallowing errors. If a tool throws and your loop returns nothing, or quietly retries on its own, the model has no idea anything went wrong and will happily call it again with the same arguments. Feed failures back as observations. An agent that can see "Error 404: not found" will adapt. An agent that sees silence will loop.
Then the cycle turns, Think runs again, and it runs with one more fact than it had before.
This pattern has a name, by the way. It's ReAct, short for reasoning and acting interleaved rather than reasoning and then acting, introduced in Yao et al., 2022 and later presented at ICLR 2023. Most modern agent frameworks are variations on it, so if a framework's docs feel familiar, that's why.
The loop diagram implies five components but only draws two of them. Here they all are:
| Part | What it is | Common mistake |
|---|---|---|
| Goal | The success condition, in text | Vague goals with no testable "done" |
| Policy | The model plus the instructions that choose the next step | Burying the goal in a wall of system prompt |
| Tools | Functions the agent may call | Too many tools, vaguely described |
| Memory | What survives between iterations | Assuming the model remembers across calls |
| Stop condition | How the loop ends | Not having one |
The model is stateless between calls.
Every request to the model API is independent. There's no session on the other end holding your conversation in its head between iterations. Everything the model "remembers" from pass one is something your loop deliberately sent again in pass two.
Once that clicks, a whole category of bugs becomes obvious. If your agent calls the same tool twice in a row, you probably dropped the first observation. If it forgets a constraint you stated at the start, you stopped resending the original goal somewhere along the way. If its context grows until the thing is slow and expensive, you're resending everything, forever, with no compaction strategy.
So memory in an agent isn't a feature the model provides. It's a decision your loop makes on every single iteration: what do I send again this time?
There are three common answers, and which one is right depends on the task:
| Strategy | What it does | What it costs |
|---|---|---|
| Full transcript | Resend everything each pass | Context grows every iteration, so cost and latency climb |
| Summarised history | Compress older passes into a summary | You risk dropping the specific detail that mattered |
| Structured state | Carry an explicit object (findings, files seen, decisions made) | More code to write, but far more predictable behaviour |
None of these is universally correct. Full transcript is a fine default for short tasks and you shouldn't over-engineer past it early. Structured state is what long-running agents tend to converge on once the transcript approach starts hurting.
An agent that can't stop isn't an agent. It's a bill.
This is the most under-taught component in the space, and it's where beginner projects go wrong most often. Not because the model is bad, but because nobody designed the exit. There are four ways a well-built loop ends, and a serious agent implements all four.
The result meets the success condition.
Ideally something other than the model verifies this, because a model asserting "I've completed the task" is a claim rather than a fact. Where you can make the check mechanical, do: run the test suite, re-query the API, diff the file. Where you genuinely can't, at least make the model state what evidence it has, so a human reviewing the trace can judge for themselves.
if verify(result): # a test run, a schema check, a diff review
return Success(result)You hit a ceiling you set in advance. You want more than one ceiling here, because they fail in different ways.
if iterations >= MAX_ITERATIONS: return Halted("iteration cap")
if tokens_used >= MAX_TOKENS: return Halted("token cap")
if elapsed() >= MAX_SECONDS: return Halted("time cap")
if cost_so_far >= MAX_COST: return Halted("cost cap")An iteration cap on its own won't save you from a single pass that reads a 500-page document. A token cap on its own won't save you from an agent sitting on a slow API call forever. The four together will.
The loop is running but nothing is changing.
Compare the last N action and result pairs. If the agent just called the same tool with the same arguments and got the same result, it has stopped accumulating information, and a loop that isn't accumulating is only spending money.
if last_two_actions_identical() and last_two_results_identical():
return Halted("no progress")N of 2 catches the common case. N of 3 is safer for agents that legitimately need to poll something.
The agent hits something a human has to decide.
This one is a design decision rather than a runtime check. Some actions shouldn't happen autonomously: spending money, sending external email, deleting data, anything you can't undo.
The part people get wrong is what to return. An escalation that says "I got stuck" is close to worthless. One that says "I found the three candidate records, here they are, deciding which to delete needs a human" hands the work back in a state someone can actually pick up.
| Failure | What it looks like | The fix |
|---|---|---|
| The infinite loop | Same tool, same arguments, forever | Add the no-progress check, and verify observations are being carried forward |
| The premature success | Agent declares victory after one pass | Add mechanical verification, don't accept self-report |
| The unverified fix | Agent says it fixed the bug but never ran the test | Make the last step run the check rather than claim the outcome |
| The fourteen retries | Identical failing call, over and over | Feed the failure back as an observation instead of retrying silently |
| The runaway cost | Task finishes, costs 40 times the estimate | Cost ceiling, plus per-iteration logging so you can see where it went |
Before writing any code, three questions are worth more than the rest of this post combined.
Does anything need to change outside the conversation? If not, you probably want a chatbot: a single model call, well prompted, with good retrieval behind it.
Do you already know the steps, in order? If yes, you want a workflow. Fixed steps in an order you chose, with the model doing the parts that need language.
Does the next step depend on what the previous one returned? If yes, and if you can't enumerate the ways it might depend, then you want an agent.
Choosing "workflow" is a win rather than a downgrade. Workflows are cheaper, faster, deterministic, and much easier to test and debug. Agents trade predictability for flexibility, and that trade only pays off when the path genuinely can't be known in advance. My rough heuristic: if you can draw the flowchart, build the flowchart.
Day 02's companion piece, Chatbot vs Workflow vs Agent: The Decision Cheat Sheet, is the full treatment, with a one-page matrix, twelve real scenarios classified, and the honest cost of getting it wrong in either direction. It's delivered under the same AGENT keyword.
Abstractions get obvious the moment you watch one run. Here's a real goal traced across six iterations, including a failure and a recovery, because clean traces teach the wrong lesson.
Goal: "Find every open PR in this repo with no reviewer assigned, and post a summary to the #eng Slack channel."
Iteration 1
Think: I need the list of open PRs before I can filter them.
Act: list_pull_requests(state="open")
Observe: [{id: 412, reviewers: ["sam"]}, {id: 418, reviewers: []}, {id: 421, reviewers: []}, {id: 425, reviewers: ["kai"]}, ...
The agent now knows something it could not have known at t=0. That's the entire point of the loop.
Iteration 2
Think: Three of the fourteen have empty reviewer lists. But "no reviewer assigned" might also mean a team was requested rather than a person, so I should check one to be sure.
Act: get_pull_request(id=418)
Observe: {id: 418, reviewers: [], requested_teams: ["platform"], title: "Bump auth deps"}
The plan just changed. PR 418 does have a review requested, just not from an individual. A workflow with a hardcoded reviewers == [] filter would have got this wrong and, more importantly, would never have noticed.
Iteration 3
Think: So the filter needs to exclude PRs with requested teams too. Let me re-check the remaining two.
Act: get_pull_request(id=421)
Observe: {id: 421, reviewers: [], requested_teams: [], title: "Fix flaky billing test"}
Iteration 4
Think: 421 genuinely has no reviewer. Checking the last candidate.
Act: get_pull_request(id=433)
Observe: Error 404: pull request not found
This is the failure. The ID came from a misreading of the iteration 1 output. Watch what happens next: the error goes back to the model as an observation rather than being silently retried.
Iteration 5
Think: 433 doesn't exist, so I misread the earlier list. Re-reading iteration 1: the third candidate was 429, not 433.
Act: get_pull_request(id=429)
Observe: {id: 429, reviewers: [], requested_teams: [], title: "Add retry to webhook sender"}
Recovery, and it only worked because the error text was visible to the model. A loop that swallowed that 404 would still be sitting there.
Iteration 6
Think: Two PRs genuinely have no reviewer: 421 and 429. The goal says post a summary.
Act: post_slack_message(channel="#eng", text="2 open PRs have no reviewer assigned: #421 Fix flaky billing test, #429 Add retry to webhook sender")
Observe: {ok: true, ts: "1731022041.000200"}
Stop: goal satisfied.
Six iterations, one wrong turn, one recovery, and nobody told it to stop. It decided, because the success condition was met.
Here's a complete agent loop. It's deliberately small enough to read in one sitting, because the single most useful thing you can learn early is that there's no magic in the middle.
messages = [ user_message(goal) ]
iterations = 0
loop:
iterations += 1
# budget exit
if iterations > MAX_ITERATIONS:
return halted("iteration cap")
# THINK
response = model.call(messages, tools)
# goal-satisfied exit
if response.wants_no_tools:
return done(response.text)
# carry the assistant's turn forward: this IS the memory
messages.append(assistant_message(response))
# ACT and OBSERVE
results = []
for call in response.tool_calls:
try:
Nine meaningful lines. Everything else in any agent framework you've used is ergonomics, observability, or error handling layered on top of this shape.
Below is the skeleton written against the Claude API. The shape is identical for any provider that supports tool calling, so if you're on something else, only the field names change.
import anthropic
client = anthropic.Anthropic()
MAX_ITERATIONS = 12
tools = [
{
"name": "list_pull_requests",
# Describe WHEN to call it, not just what it does. The model
# selects tools by reading these descriptions.
"description": (
"List pull requests in the repository. "
"Call this when you need to see which PRs exist before "
"inspecting or filtering them."
),
"input_schema": {
"type":
Four details in there matter more than they look.
Every tool result has to carry the tool_use_id of the call it answers, and mismatched IDs get rejected outright. All results from one turn go back in a single message, because splitting them across several messages teaches the model to stop making parallel calls. Errors go back with is_error: True rather than being dropped, since an invisible failure is a guaranteed retry loop. And those two messages.append lines are the memory: delete them and the agent re-derives the task from scratch on every pass, which is the classic infinite loop. The agent isn't confused when that happens. It's genuinely seeing the task for the first time, every time.
This is a skeleton, not a framework. It has no retries, no logging, no cost tracking, and no human approval gate. Add those next, but add them to this, so you always know what's underneath.
Run this against your own project. Should take five minutes.
How to read your answers: if any of 1, 3, or 4 came back "no", you don't have an agent yet, you have a workflow or a chatbot with extra steps. If any of 5, 6, or 8 came back "no", you have an agent but not a safe one, so fix the exits before running it unattended. And if 7 came back "yes", that's the most valuable finding on the list. Go build the simpler thing.
| Term | Definition |
|---|---|
| Agent | A system where the model chooses the next step based on observations, and decides when the goal is met |
| Workflow | Fixed steps in an order chosen by a human at build time, with the model doing language-shaped parts |
| Tool | A function the agent may call to affect or observe something outside the conversation |
| Tool call | A single structured invocation of a tool, with arguments, produced by the model |
| Observation | The result returned to the model after a tool call, including errors |
| Trace | The full ordered record of one run: every thought, call, and observation |
| Iteration (or pass) | One complete turn of the loop: think, act, observe |
| Stop condition | The rule that ends the loop: goal met, budget spent, no progress, or escalation |
| Policy | The model plus its instructions, meaning whatever decides the next action |
| Context | Everything sent to the model on a given call |
| State | What the loop deliberately carries forward between iterations |
You are here: Day 01.
Days 01 to 05 cover fundamentals: what an agent is, how it differs from a chatbot, the loop, tools, and prompting for reliability. Days 06 to 14 move into agent engineering, meaning planning, memory, retries, guardrails, evaluation, and observability. Days 15 to 22 cover protocols and integration, or how agents connect to the systems they act on. Days 23 to 30 are about production, and what changes once real users, real credentials, and real money are involved.
Day 02 answers the question this post raises most often: chatbot, workflow, or agent, and which should you actually build? It's the companion piece to this one, under the same AGENT keyword.
MewCP builds infrastructure for the production end of this journey, but that's Day 23 onward. For now, go build a loop and watch it run.
Follow @mewcp_ai for the rest of the series.
| ReAct | Reasoning and acting interleaved rather than sequential (Yao et al., 2022) |
| Autonomy | How much of the loop closes without a human in it |