Open almost any AI agent example and the first line of real code is async def. Then every interesting call has an await in front of it, and the whole thing ends with asyncio.run(main()). If that syntax makes a simple tutorial feel advanced, you are not missing anything deep — you are missing two keywords that nobody stopped to explain. This async and await in Python primer explains them, so agent code reads like plain English.
You do not need to master async to build agents in 2026. You need to read it, because the LLM SDKs and frameworks you will use ship async-first examples. By the end of this short read you will know what async def and await mean, when they help, and the two mistakes that catch every beginner.
- You can read a basic Python function and run a
.pyfile — new to that? The Python for AI agents primer covers the basics - That’s the whole list — this primer assumes no async experience at all
async defmakes a coroutine: calling it doesn’t run the body, it hands you a coroutine you have toawait.awaitmeans “pause here until this finishes” and lets the event loop run other work while you wait — that’s concurrency, not parallelism.asyncio.run(main())is the on-ramp: it starts the event loop and runs your top-level async function.- The two beginner traps are forgetting
await(nothing runs) and calling slow blocking code inside async (everything freezes).
What async and await actually mean: waiting without freezing
Picture a barista taking an order, starting the espresso machine, and — instead of standing there watching it drip — taking the next customer’s order while the machine works. Same person, one counter, but no idle waiting. That is async: one thread that switches to other work whenever it would otherwise be stuck waiting.
The thing it waits on is almost always I/O (input/output) — a network request, a database query, an LLM API call. These are slow not because your computer is busy, but because it’s waiting on someone else’s server. Async lets a single program fire off one API call and start another while the first is still in flight. Async is about not wasting time waiting, not about doing two calculations at once. That second thing is parallelism, and it’s a different tool (threads and processes).
This is exactly why agent code is async-first: an agent’s whole life is waiting on model and tool calls over the network. Async is the natural fit.
async def and await: the only two keywords
Here are the two pieces of syntax you’ll read constantly. A function defined with async def is a coroutine. The surprise for newcomers: calling it does not run the body.
import asyncioasync def get_weather(city: str) -> str:await asyncio.sleep(1) # pretend this is a slow API callreturn f"It's sunny in {city}."result = get_weather("Pune") # ⚠️ does NOT run — result is a coroutine object
To actually run a coroutine, you await it from inside another async function. await means “run this, and pause me until it returns — and while I’m paused, let other work happen.”
async def main():report = await get_weather("Pune") # runs it, waits for the resultprint(report)
Running async code: asyncio.run
You can only await inside an async function — so how does the first one start? That’s what asyncio.run() is for. It starts the event loop (the scheduler that runs coroutines and switches between them) and runs your top-level coroutine to completion.
import asyncioasync def main():print(await get_weather("Delhi"))asyncio.run(main()) # the single entry point that boots the loop
The rule of thumb: asyncio.run(main()) appears exactly once, at the very bottom of the script. Everything async flows out of that one call.
Doing things at once: gather and TaskGroup
The real payoff comes when you await several slow things together instead of one after another. The modern, safe way is asyncio.TaskGroup (Python 3.11+), which starts tasks and waits for all of them — and cancels the rest if one fails.
async def main():async with asyncio.TaskGroup() as tg:t1 = tg.create_task(get_weather("Pune"))t2 = tg.create_task(get_weather("Delhi"))print(t1.result(), t2.result()) # both ran concurrently
Run two one-second calls sequentially and it takes two seconds; run them in a TaskGroup and it takes about one. For an agent calling several tools at once, that difference is the whole point.
The two mistakes that bite beginners
Almost every async bug a beginner hits is one of these two, so learn to spot them now.
Forgetting await. Call a coroutine without await and the body never runs — you get a coroutine object and a “coroutine was never awaited” warning, with no error and no result. If an async call seems to do nothing, check for a missing await before anything else.
Blocking the loop. The event loop only switches at an await. Drop a slow synchronous call into an async function — time.sleep(5), requests.get(...), a heavy CPU loop — and the whole program freezes, because nothing yields control back to the loop.
When you don’t actually need async
Async is not free — it adds keywords, an event loop, and a class of bugs. If your script makes one API call and exits, plain synchronous code is simpler and just as fast. Reach for async when you’re doing many I/O operations and waiting is the bottleneck: several tool calls at once, a web server handling many requests, a batch of documents to fetch. That’s why agent frameworks default to it — but a small one-shot script doesn’t need it.
Quick recap
Everything above, in five lines:
async defdefines a coroutine; calling it returns an object you mustawait.awaitruns a coroutine and pauses until it’s done, freeing the loop for other work.asyncio.run(main())boots the event loop — once, at the bottom of the script.TaskGroupruns several coroutines concurrently and fails safely.- Watch for a missing
await(nothing runs) and blocking calls (everything freezes).
Frequently Asked Questions
What do async and await actually do? async def makes a coroutine that doesn’t run until awaited; await runs it and pauses the caller until it returns, letting the event loop do other work meanwhile.
Do I need async to build AI agents? You need to read it, because frameworks ship async examples. Writing your own comes later, when you actually need concurrency.
Async or threads? Async (single thread, switches at await) suits I/O-bound work like API calls; threads suit CPU-bound work. Agents are I/O-bound, so async fits.
Why doesn’t my coroutine run? You called it without await. Add await inside an async function, or run the top one with asyncio.run().
Conclusion
Async stops being intimidating the moment you read async and await as one idea: async def marks work that can pause, await is where it pauses and resumes, and asyncio.run starts the whole thing. That’s enough to follow any agent quickstart line by line — which is the real goal, since you’ll read far more async code than you write at first.
What’s the first piece of async agent code you want to actually understand — an LLM call, a tool, a whole agent loop? Tell me in the comments.
- Need the Python basics first? The Python for AI agents primer covers dicts, functions, and loops.
- Ready to use it? Call an LLM in Python is your first real async-friendly agent code.
- Connecting to real tools? The MCP client tutorial leans on
async/awaitthroughout.



