Lab 01 — Async Streaming Agent Service
Phase 12 · Lab 01 · Phase README · Warmup
The problem
Agents are I/O-bound (waiting on LLM and tool calls), so a production agent service is an async
service: it serves many concurrent requests on few threads, streams tokens as they're generated,
decouples work with events, dedupes retries with idempotency keys, and holds session state
in an external cache. Build that service with stdlib asyncio only — deterministic and offline,
with a real-FastAPI sketch left as an extension.
What you build
| Piece | What it does |
|---|---|
stream_agent / sse_stream / format_sse | async token streaming in Server-Sent-Events shape |
AgentService | request handling under an asyncio.Semaphore (concurrency + backpressure) + metrics |
| idempotency | a repeated idempotency_key returns the cached result without re-running the model |
TTLCache | session/result state with an injected clock TTL (Redis-shaped) |
EventBus / Subscription | in-process pub/sub with fan-out to multiple subscribers |
| health / graceful drain | readiness + shutdown() that waits for in-flight requests |
File map
| File | Role |
|---|---|
lab.py | your implementation (fill the # TODOs) |
solution.py | reference + an asyncio.run demo (concurrency, idempotent replay, TTL expiry, pub/sub) |
test_lab.py | 26 tests (wrapped in asyncio.run, no pytest-asyncio needed) |
requirements.txt | pytest only |
Run it
pytest test_lab.py -v
LAB_MODULE=solution pytest test_lab.py -v
python solution.py
Success criteria
-
stream_agentyields token events then a done event, in SSE format. - The semaphore caps concurrency (max in-flight never exceeds the limit).
- A repeated idempotency key replays the cached result without re-invoking the model.
-
TTLCachehits/misses/expires by the injected clock; the event bus fans out to all subscribers in order; graceful drain waits for in-flight work. -
All 26 tests pass under both
labandsolution.
How this maps to the real stack
stream_agent≈ FastAPI'sStreamingResponseover Server-Sent Events (the wire format chat UIs use); the async model is theasyncioevent loopuvicornruns.- The
TTLCache≈ Redis for session/conversation state and result caching; theEventBus≈ Redis pub/sub, Kafka, or a cloud queue for event-driven agent workflows. - Idempotency keys ≈ the Stripe-style dedupe pattern (essential once agents retry — Phase 08).
- The concurrency semaphore ≈ backpressure protecting downstream LLM rate limits; graceful drain ≈ Kubernetes readiness/termination handling.
Limits. A real service adds real network I/O, a real event broker, distributed state, and autoscaling; the async/streaming/idempotency/pub-sub mechanisms are exactly these.
Extensions (your own machine)
- Wire the real-FastAPI
app.pysketch:@app.post+StreamingResponsemapping to your stdlib version. - Swap the in-process bus for Redis pub/sub; swap the
TTLCachefor real Redis. - Add a rate limiter and per-tenant queues (Phase 13/14).
Interview / resume signal
"Built an async streaming agent service — SSE token streaming, an
asyncioconcurrency/backpressure layer, idempotency-key dedupe, a TTL state cache, and an event-bus pub/sub — the FastAPI/asyncio/ event-driven production shape, from first principles."