AI Agents, Explained From Scratch (With Google's ADK)
Ask a chatbot "what's the weather in Paris?" and it might tell you what Paris weather is usually like in October. Ask an agent the same thing and it will go and check.
That's the whole difference, and it's a bigger one than it sounds. A chatbot can only talk. An agent can do: look things up, fill in forms, book the flight, file the ticket. It decides which steps are needed, takes them, looks at what happened, and keeps going until the job is finished.
This piece explains how that works, in plain English, using Google's Agent Development Kit — ADK for short. ADK is a free, open-source toolkit for building agents in ordinary code. You don't need to know how to code to follow along: every idea below comes with something you can click, and the code snippets are there so you can see that none of this is magic.
An agent is a model, an instruction, and some tools
Strip away the hype and every agent is made of three things:
- A model — the large language model doing the thinking. It reads, reasons, and decides what to do next. (If you've read why LLMs hallucinate, this is the same kind of model.)
- An instruction — a job description in plain English: who the agent is, what it's for, how it should behave.
- Tools — the agent's hands. A tool is just a small program the model is allowed to use: check the weather, search the web, send an email.
ADK's job is to give those three things a tidy structure, so you can test them, combine them, and put them into production.
Three ingredients, one agent
Tap an ingredient to see where it lives in a real, complete ADK agent.
The large language model doing the thinking. ADK is built around Google's Gemini models, but it can plug in other providers — Claude, GPT, open-weight models — so you can swap the brain without rewriting the agent.
from google.adk.agents import Agent def get_weather(city: str) -> dict: """Returns the current weather for a city. Args: city: The city name, e.g. "Paris". """ return {"temp_c": 17, "condition": "cloudy"} root_agent = Agent( name="weather_assistant", model="gemini-2.5-flash", description="Answers questions about the weather.", instruction="You are concise. Always use tools for live data.", tools=[get_weather],)Notice how the tool is described: in a paragraph of English inside the function (programmers call this a docstring). The model never reads the tool's code. It reads that description and decides, on its own, when the tool would help. Writing a good tool description is a lot like writing a good prompt — vague in, vague out.
What actually happens when you ask an agent something
Here's the part that makes an agent an agent. When your message arrives, ADK doesn't just pass it to the model and hand back whatever comes out. It runs a loop:
- Think. The model reads your request and its list of tools, and decides what to do.
- Act. If it needs information or needs to change something, it asks to use a
tool — "call
get_weatherwith city = Paris." - Observe. ADK runs the tool and hands the result back to the model.
- Repeat until the model decides it has enough, then answer.
A simple question goes round once. A two-part request goes round twice, each step informed by the last. Try it:
Inside the agent loop
A scripted replay of what an ADK agent does between your message and its answer: think, call a tool, read the result, think again. Flip the switches to add or remove guardrails, then replay a request.
Guardrails (callbacks)
session.state
The agent's scratchpad for this conversation.
Two things are worth noticing in that demo.
First, the agent's answer to the Tokyo request depends on a tool result it didn't have when it started. It only knew to check the weather because it reasoned that packing advice needs it. Nobody programmed that sequence; the model chose it. That's the power of agents — and, as you'll see, the reason you want guardrails.
Second, the panel labelled session.state fills up as the agent works. That's the agent's scratchpad for the conversation, and we'll come back to it.
When one agent isn't enough: building a team
A single agent with too many tools and too long an instruction gets confused, the same way a new hire given fifteen responsibilities on day one does. The fix is the same as in any organisation: specialise, and give someone the job of coordinating.
In ADK, a team of agents is arranged as a tree. Every agent has exactly one parent. At the top sits a coordinator that reads your request and passes it to the right specialist below it — deciding purely by reading each specialist's one-line description.
A team of agents is a tree
Every agent has exactly one parent. Pick any agent in this travel-company example to see what it does.
LlmAgent
The coordinator
Like a receptionist who listens to you, then sends you to the right department.
A model-powered agent at the top of the tree. It reads the one-line description of each agent below it and decides which should handle your request. You don't write any routing rules — the model does the routing by reading.
root_agent = LlmAgent( name="root_agent", model="gemini-2.5-flash", instruction="Send each request to the right specialist.", sub_agents=[research_agent, report_pipeline, compliance_check],)There are only four kinds of building block, which is part of what makes ADK easy to reason about:
- LlmAgent — a thinking agent, powered by a model. Flexible, but it won't do things the same way every time.
- SequentialAgent, ParallelAgent and LoopAgent — workflow agents. They don't think at all. They just run the agents inside them in a fixed pattern.
- Custom agents — your own rules, written as ordinary code, for anything a model shouldn't be improvising.
Teams that follow a script: workflow agents
The three workflow agents deserve a closer look, because they solve a problem that catches out a lot of first-time builders.
You could write one big instruction — "first outline the report, then draft it, then polish it" — and trust a single model to follow it. Usually it will. Sometimes it will skip a step, or do them in a different order, or decide the outline is good enough to be the report. Every one of those decisions also costs a model call.
A workflow agent takes the decision away from the model. The order is written in code, so it happens the same way every time, and no model is paid to decide it.
Three ways to run a team
Workflow agents don't call a model to decide what happens next — they follow the order you wrote. Pick one and press run.
Each step waits for the one before it and picks up its notes from session.state.
outline_agent
writes an outline
waiting
draft_agent
reads the outline, writes a draft
waiting
polish_agent
reads the draft, polishes it
waiting
session.state
emptyModel calls spent deciding the order: zero. The order is plain code.
pipeline = SequentialAgent( name="report_pipeline", sub_agents=[outline_agent, draft_agent, polish_agent],)# each agent sets output_key, and the next one reads it- Sequential is an assembly line: when order matters, and each step builds on the last.
- Parallel is splitting the work between friends: when the jobs are independent, and you'd rather wait for the slowest than for all of them added up.
- Loop is a writer and an editor: when you want to keep improving something until it's good enough, with a limit so it can't go on forever.
The neat part is that these nest. A sequential pipeline can contain a parallel research step and a looping review step — which is exactly what the tree in the previous figure did. Thinking agents for judgement, workflow agents for structure.
How an agent remembers
"Memory" gets used loosely when people talk about AI, and in ADK it means three separate things. Mixing them up is the most common beginner mistake, so it's worth sorting them out once:
Session, state and memory
Three different things that all get called “memory” in conversation. Pick one to see where it sits.
Memory — everything Alex has ever discussed
Session · Monday · Plan a Tokyo trip
- • You: flights to Tokyo?
- • search_flights(…)
- • Agent: AF274, €612
State
Session · Today · Book it
- • You: book AF274
- • book_flight(…)
- • Agent: booked, X7QK2B
State
Session
One conversation's transcript.
Everything that happened in a single conversation, in order: each message, each tool call, each reply. ADK stores it as a list of events you can replay line by line — the audit trail of what the agent did and why.
Lasts for one conversation.
session = await session_service.create_session( app_name="travel_app", user_id="alex",)for event in session.events: print(event.author, event.content) # the full historyThe short version: a session is one conversation's transcript. State is a notepad the agents in that conversation share — it's how one agent passes its work to the next. Memory is the long-term archive, searchable across every conversation you've ever had with the agent.
Guardrails: six moments you can step in
Back in the demo, the switches on the right were callbacks: small pieces of your own code that ADK runs at fixed moments — just before the model is called, just after a tool returns, and so on. They're where the safety and housekeeping live: hiding personal data, blocking risky actions, keeping costs in check, logging what happened.
Six moments you can step in
In the order they fire during one turn. The model and tool hooks repeat each time the agent goes round its loop.
Typically used to: Hide personal data, catch prompt-injection tricks, cap how much you spend.
The rule: return None and ADK carries on normally. Return something, and that something replaces the step.
def before_model(callback_context, llm_request): text = llm_request.contents[-1].parts[0].text if looks_like_injection(text): return LlmResponse(...) # a canned reply; the model is never called return NoneThe rule that makes callbacks easy to reason about: if your callback returns nothing,
ADK carries on as if it weren't there. If it returns something, that something
replaces the step. Return a canned reply from before_model and the model is never
called. Return a result from before_tool and the tool never runs.
This is why the instruction "never book without asking" isn't a real safeguard. An instruction is a request the model will usually honour. A callback is code, and it always runs.
Getting one running
Because an ADK agent is just a small program, the path from idea to something real is short:
- Try it on your own computer. Running
adk webopens a chat window in your browser, alongside a step-by-step trace of everything the agent thought, every tool it called and every note it wrote — the same view as the demo above, but real. - Test it properly. Agents don't take the same path every time, so "it worked
once" proves very little.
adk evalreplays a set of example requests and checks both the final answers and the route the agent took to reach them — did it call the right tools, in a sensible order? - Put it online. The same code can be packaged up and deployed to Google Cloud, either onto servers you control or onto Google's fully managed Agent Engine, which looks after the conversations, memory and scaling for you.
The version worth remembering
An agent is a model with a job description and a set of tools, running a loop: think, act, look at the result, repeat. Everything else in ADK exists to make that loop dependable — teams so no single agent is overloaded, workflow agents so the order is predictable, state and memory so nothing is forgotten, and callbacks so the things that must never happen, don't.
Did it stick?
Five questions. Pick an answer to see why it's right or wrong.
1. You need three independent lookups done as fast as possible. Which agent runs them?
2. A tool must never run without a person's approval. Where do you enforce that?
3. What's the difference between state and memory?
4. How does a coordinator agent decide which agent below it should take a request?
5. Why use a SequentialAgent instead of asking one agent to do three steps in order?
If you want to build one, Google's official ADK documentation starts with a quickstart that gets a working agent running in about ten minutes.