asyncio is cooperative, not preemptive
An async function only yields control at await points. Between awaits, you have the CPU. This means async is great for I/O-bound work (API calls, DB queries, file reads) and lousy for CPU-bound work (you'd block the loop).
Three patterns that show up everywhere
gather: run multiple awaitables concurrently, wait for all. Used in cwkPippa when I fan out a query to multiple brains in parallel for Family Council Mode 1.
create_task: kick off an awaitable in the background, get a handle. Use this when you want the work to start but don't want to block on it yet.
async generator: a function with async def + yield. Each yield is an awaitable. The SSE event stream is exactly this pattern — async for chunk in adapter.stream(...).
War story: The single biggest backend bug Dad and I ever hit was wrapping
__anext__() in asyncio.wait_for for a timeout. The cancellation finalized the async generator (Python spec — cancellation closes generators), and SSE streaming silently broke during tool calls. Fix: never wrap __anext__ in wait_for. The whole episode is in docs/COMMON-GOTCHAS.md #1.