An autonomous AI agent is a system that perceives its environment, decides what to do, acts, and loops — pursuing a goal without step-by-step human…
Define an autonomous AI agent and its four core characteristics: perception, decision-making, action, and autonomy. You'll anchor the definition to a concrete running example: a web-research agent given the goal 'find the top 3 competitors for a SaaS product.'
Defines an autonomous AI agent and its four core traits — perception, decision-making, action, and autonomy — anchored to a web-research example.
Why this matters: Everything you build in this lesson runs on this loop; getting the definition sharp prevents every downstream confusion.
An is a program that takes a goal and decides its own steps. It uses tools and keeps going until the goal is met.
A chatbot answers one question and stops. An agent loops: it acts, checks the result, then decides next.
Before reading on — predict: what makes an agent different from a chatbot?
Every autonomous AI agent has four core traits: , decision-making, , and .
These four traits work together inside the : perceive → decide → act → observe → repeat.
Goal given to the agent: "Find the top 3 competitors for a SaaS product."
Here is how each trait shows up:
A chatbot would answer with a guess from memory. The agent actually searches, reads live pages, and returns sourced results.
# Naive approach: ask the LLM directly, no tools goal = "Find the top 3 competitors for a SaaS product." response = llm.complete(goal) print(response)
Calling an LLM directly gives one static answer — no loop, no tools, no checking.
This is the pattern. It breaks the moment the task needs fresh data or multiple steps.
Output: 'The top 3 competitors are likely Salesforce, HubSpot, and Zoho.' — The LLM guesses from training data. It has no live search tool, so results may be outdated or wrong for your specific product. This is a one-shot answer, not an agent.
def search(query): ... # tool: returns web results def llm_decide(goal, obs): ... # picks next action or "DONE" goal = "Find top 3 competitors for a SaaS product." observation = "" while True: action = llm_decide(goal, observation) # decide if action == "DONE": break # termination observation = search(action) # act + observe
agent loop: decide → act → observe → repeat until the is met.
Each iteration, the LLM sees the goal and the latest observation — that's what makes it an agent, not a one-shot call.
It holds the raw search results for the first query the LLM chose — e.g. a list of web snippets about SaaS competitors. The agent reads those results on the next iteration to decide its next step.
def summarize(text): ... # tool: returns a short summary def llm_decide(goal, obs): ... goal = "Summarize the homepage of each top competitor." observation = "" while True: action = llm_decide(goal, observation) if action == "DONE": break # TODO: call the right tool and store the result # Hint 1: which tool fits a 'summarize' goal? # Hint 2: the result must go into `observation`
tool and goal.
observation is the core skill of every agent you'll build.
Next up: the Perceive–Decide–Act loop — you'll trace every step of the web-research agent in slow motion, seeing exactly what the agent reads and decides at each turn.
observation = summarize(action) ← CHANGED LINE: calls summarize (not search) because the goal is summarization, not web search. The result goes into observation so the LLM sees it on the next iteration. Everything else stays the same — the loop structure is identical.
Three to know before you build:
Trace the full perceive → decide → act → observe cycle step by step, using the web-research agent as a fully worked example. You'll see exactly what the agent reads, what it decides, what tool it calls, and how the result feeds back into the next step.
A step-by-step walkthrough of the perceive–decide–act–observe loop that every autonomous agent runs.
Why this matters: Understanding this loop lets you trace, debug, and build agent behavior — it is the engine behind every agent you will write.
Module 1 defined an by four traits: , decision-making, , and .
This module zooms in on the engine behind those traits: the . How does an agent actually cycle through those four traits, step by step?
An agent doesn't answer once and stop. It runs a four-step loop until the job is done.
The step is what makes the loop self-correcting. Without it, the agent acts blind — it never learns what actually happened.
ReAct (Reason + Act) makes the decide step explicit.
The model writes Thought: before every action. This names why it's choosing this tool now.
After the tool runs, it reads Observation: and writes a new Thought:. It loops until it writes Final Answer:.
Goal given to the agent: "Find the top 3 competitors of Notion."
web_search("Notion competitors 2024").Notice: the observation from step 4 is what unlocks the decision in step 6. Without it, the agent has no evidence to stop.
# Stage 1 — one perceive→decide→act→observe cycle goal = "Find the top 3 competitors of Notion." memory = [] # starts empty # PERCEIVE: build context from goal + memory context = goal + "\n" + "\n".join(memory) # DECIDE: LLM reasons and picks a tool call thought = llm_think(context) # thought → 'I should search for Notion competitors.' # ACT: run the chosen tool result = web_search("Notion competitors 2024") # OBSERVE: store result so next loop can read it memory.append(f"Observation: {result}")
This shows one full turn of the loop. The key move: the tool result goes into memory so the next iteration can read it.
memory contains one string: "Observation: <search results listing Coda, Confluence, ClickUp>". The goal and thought are NOT in memory yet — only the tool's output is appended here. That observation becomes the new input on the next perceive step.
# Stage 2 — repeat the loop; stop when LLM says 'Final Answer' goal = "Find the top 3 competitors of Notion." memory = [] for _ in range(5): # safety cap: max 5 turns context = goal + "\n" + "\n".join(memory) thought, action = llm_decide(context) # returns thought + next action if action["type"] == "final_answer": print("Done:", action["content"]) break result = run_tool(action) # ACT memory.append(f"Observation: {result}") # OBSERVE
Stage 2 adds the loop and a stop condition. The delta from Stage 1: llm_decide now returns an action type, and final_answer breaks the loop.
The loop runs all 5 iterations and exits without printing an answer — the agent silently gives up. This is the 'infinite loop / no termination' failure mode. The safety cap (max turns) is what prevents it from running forever, but it also means the task goes unfinished if the cap is too low.
# Completion task — fill in the missing OBSERVE line goal = "Find the top 3 competitors of Notion." memory = [] for _ in range(5): context = goal + "\n" + "\n".join(memory) thought, action = llm_decide(context) if action["type"] == "final_answer": print("Done:", action["content"]) break result = run_tool(action) # TODO: store the result so the next loop can read it
This is a small variation of Stage 2 — the loop structure is the same, but the observe line is missing. Supplying it is the whole point of the loop.
Next up: the goal shapes every decision inside this loop — and sets the rule for when to stop. That's exactly what module 3 unpacks.
memory.append(f"Observation: {result}")
Changed line: the TODO becomes this append. Without it, memory stays empty every iteration — the agent re-reads only the bare goal and loops forever with no new information. This is the crux of the loop: feeding the tool output back in is what makes each turn smarter than the last.
Three ways this loop fails:
"Error: rate limit exceeded" but treats it as valid and hallucinates.web_search in circles. Without a max-turns cap, it drains your API budget.Explain how a goal shapes every decision in the loop and sets the termination condition. Using the same web-research agent, you'll complete a partial worked example — given a half-finished loop trace, you'll identify the missing step and the stopping condition.
Explains how a goal shapes every decision in the agent loop and defines the condition that ends it.
Why this matters: Without a clear goal and stop condition, your web-research agent will loop forever or return incomplete results — this module shows you how to write both.
Module 2 traced the full cycle: perceive → decide → act → observe.
That loop runs again and again — but something must tell it when to stop. That's this module's question.
A is the single statement that shapes every decision in the loop.
It does two jobs: it tells the agent what to do and it defines the — the test that ends the loop.
Without a clear goal, the agent has no way to judge whether a result is good enough.
Goal: "Find the top 3 competitors for Acme SEO and return each competitor's name plus one key differentiator."
Watch how the goal filters every decision:
At step 4, the agent doesn't stop — the goal says 3, it only has 2. The termination condition is the gatekeeper.
goal = "Find top 3 competitors; return name + differentiator each." results = [] while True: query = decide_next_search(goal, results) hits = search_web(query) # tool call results = extract_findings(hits, results) # TODO: write the termination condition here if ___________________________: break return results
This is the web-research agent's loop — one line is missing. Your job: supply the termination condition that matches the goal.
CHANGED LINE:
if len(results) >= 3 and all('differentiator' in r for r in results):
WHY: the goal has two requirements — count (3) and completeness (each entry has a differentiator). Both must be true before the loop ends. A count-only check would stop too early if an entry is missing its differentiator.
Three ways a missing or weak termination condition causes real problems:
len(results) >= 3 passes even when entries have no differentiator. You see: output looks complete but data is missing — silent failure.if turns > 10: break), and (3) the loop cannot pass the test with incomplete data.Compare autonomous AI agents to traditional one-shot AI calls across four dimensions: multi-step vs. single-step, tool use, memory, and failure modes. You'll revisit the web-research agent and contrast it with a plain GPT API call doing the same task — then decide which fits a given scenario.
Compares autonomous AI agents to traditional one-shot AI calls across four dimensions: steps, tools, memory, and failure modes.
Why this matters: Helps you decide when your SEO/GEO page project needs a full agent versus a simpler, cheaper API call — and what can go wrong with each.
Decision this forces: Should this task use an autonomous AI agent or a simpler one-shot AI call?
Answer: a tells the agent "goal met, stop looping." Without one, the agent loops forever — a you'll see again here.
Now the question: how is a looping agent different from a plain AI API call?
A call sends one prompt, gets one answer. No loop, no tools, no memory.
Task: "Find the top 3 competitors of Notion and summarize their pricing."
The agent used live data and 4+ steps. The traditional call used one step and stale training data.
# Traditional AI call — one prompt, one reply response = llm.complete( prompt="Find the top 3 competitors of Notion and summarize pricing." ) print(response.text) # Output: stale training-data answer, no live search
One function call, one reply — the model never touches a tool or loops.
The output is whatever the model memorized during training. It cannot fetch today's prices.
It prints the old pricing from training data — the model has no way to check live information. The answer looks confident but may be wrong.
# Autonomous agent — loops until goal is met def web_search(query): ... # tool: returns live results agent = Agent( goal="Find top 3 Notion competitors and summarize pricing.", tools=[web_search], ) result = agent.run() # loops: search → observe → decide → search … print(result) # live, multi-step answer
The agent gets a and a list of — then runs the loop itself until the termination condition is met.
Compare Stage 1 and Stage 2: the only additions are a goal, a tool list, and agent.run(). Those three lines unlock multi-step, live-data .
The agent treats the empty result as an observation, re-enters the decide step, and tries a different query — it does NOT stop. A traditional call would just return an empty answer with no retry.
# Task: "Is today's weather in London good for a picnic?" # You decide: agent or traditional call? # Option A — traditional call response = llm.complete( prompt="Is today's weather in London good for a picnic?" ) # Option B — agent agent = Agent( goal="Is today's weather in London good for a picnic?", tools=[TODO], # ← what goes here, and why? )
One of these two options is wrong for this task. Your job: fill in the TODO and explain which option to use.
Option B is correct. TODO = [get_weather]. Changed line: tools=[get_weather] — the model needs a live weather tool because training data has no today's forecast.
Option A would give a hallucinated or stale answer. Option B uses one tool call inside the agent loop to fetch real data, then answers. This is the minimum viable agent: one tool, one loop iteration, a clear termination condition (weather fetched → done).
Bonus check: make sure get_weather is actually defined before running — otherwise you hit the hallucinated-tool-call failure mode.
Two are unique to agents — a one-shot call never hits either.
get_live_stock_price()). The call fails silently or throws a NameError — the agent may retry forever.Before reading the summary: from memory, name the four core characteristics of an autonomous AI agent, sketch the four steps of the agent loop in order, and state the one thing that separates an agent from a traditional AI call. Then check your answer against the buildOrder below.
Apply what you learned to SEO/GEO page — the page title is the H1 and the exact search query. OPEN with a 40-60 word self-contained, quotable answer capsule that names the entity explicitly in sentence one (no "in this lesson…" preamble) — this is the sentence an LLM lifts. Name the entity by its full name every time (not pronoun-only). Structure each section as ONE sub-question (How it works · X vs Y · When to use it · Example · Common mistakes), each opening with its own 1-2 sentence direct answer then depth. End with a genuine 3-5 question FAQ phrased as real user questions ending in "?" (these become FAQPage schema + the knowledge check). Prefer quotable specifics — concrete numbers, defaults, version names, one short snippet — over vague prose. Self-contained for a reader who arrived cold from a search engine or an LLM. Intent = definition: define the named entity + how it works + a concrete example. Target query: "autonomous ai agents"..
In one sentence, define an autonomous AI agent. Your answer should name what it is, what it does on its own, and what guides it toward stopping.
A strong answer names three things: the AI-powered system, the perceive-decide-act loop it runs on its own, and the goal or termination condition that tells it when to stop. Missing any one of these leaves out a core characteristic. A chatbot, by contrast, responds once per human turn and does not act on its own between turns.
A developer needs to classify the sentiment of a single customer review and return a label. Which approach fits best?
A single, fully specified task with a known input and a single output is the textbook case for a one-shot AI call — no environment to perceive, no tools to invoke, no loop needed. Agents add overhead (looping, tool calls, termination logic) that brings no benefit here and introduces extra failure modes like infinite loops. Accuracy is not an inherent advantage of agents, and token length is irrelevant to the agent-vs-API decision.
In the Perceive-Decide-Act loop, what is an 'observation' and why does it matter?
step 1: agent searches the web for 'best running shoes 2024'
step 2: search returns 10 result snippets
step 3: agent reads snippets, decides to click the top result
An observation is the data the agent receives back from the environment after taking an action — here, the 10 snippets returned by the search tool. It matters because the agent feeds this new information into its next decision cycle; without it, the loop cannot adapt. The reasoning in step 3 is the 'decide' phase, not the observation. The original query is the goal input, not an environmental observation. Ignoring observations would make the loop open-ended and blind.
An agent is given the goal: 'Research the topic.' It has access to a web-search tool and no other stopping condition. What is the most likely failure mode?
Without a clear termination condition — such as 'return a 200-word summary after 5 sources' — the agent has no signal that the goal is met, so it can keep searching forever. This is the classic infinite-loop failure mode unique to autonomous agents. A vague goal does not cause a refusal; agents attempt it anyway. One search and stop would require a built-in step limit, which was not specified. Hallucinating a tool call is a real failure mode but is not triggered specifically by a missing stopping condition.
Which of the following is a failure mode that can affect an autonomous AI agent but NOT a traditional single-shot AI API call?
A hallucinated tool call — where the agent invents and attempts to invoke a tool that was never defined — is only possible when an agent has a tool-use mechanism, which a traditional API call does not have. Factual errors, context-window overflows, and wrong output formats can all occur in a plain API call as well as in an agent. This makes hallucinated tool calls a failure mode unique to the agent architecture.