Most LangGraph tutorials show you how to define a graph and run it in a Jupyter notebook. That's useful for learning. But there's a meaningful gap between "it runs in my notebook" and "it runs reliably at 2am when 12 users are triggering concurrent agent sessions."
This article covers that gap. Specifically, how we structure the serving layer for LangGraph-based agents in production — using FastAPI as the HTTP interface, PostgreSQL for state persistence, and Celery for long-running background execution.
This is the architecture IRIS runs on. Bukan aspirational architecture — ini adalah benda yang sebenarnya deployed.
Why FastAPI specifically
Python's async framework landscape has several options — Django, Flask, FastAPI, Litestar. For AI agent backends, FastAPI wins on three specific dimensions:
- Native async support. LangGraph agent execution is inherently async — waiting on LLM API calls, tool calls, database queries. FastAPI's ASGI foundation handles this cleanly. Flask's sync-by-default creates threading complexity when wrapping async agent code.
- Streaming response support. FastAPI has first-class
StreamingResponseand SSE support. Streaming LangGraph output through to the client requires this — it's not an afterthought bolted on. - Automatic schema generation. FastAPI generates OpenAPI docs from type hints. For an API that other services or frontend clients will consume, this means your API is self-documenting without extra work.
System architecture overview
Streaming agent output with Server-Sent Events
This is the most important piece to get right for user experience. An agent that processes a complex task might take 15–45 seconds. Without streaming, the user sees a blank screen and a spinner. With SSE streaming, they see the agent's reasoning and output appearing progressively — which transforms the UX from "broken?" to "working."
The X-Accel-Buffering: no header is important if you're behind Nginx as a reverse proxy. Without it, Nginx will buffer the SSE response and the client won't receive events until the buffer fills — which defeats the purpose of streaming entirely. This is a production gotcha that's easy to miss in local development where there's no proxy.
PostgreSQL checkpoint backend for persistent state
By default, LangGraph uses in-memory checkpointing. This means agent state is lost when the process restarts. For production, you need a persistent backend.
We use langgraph-checkpoint-postgres — the official Postgres checkpoint implementation. Setup is straightforward:
With PostgreSQL checkpointing, every agent state transition is persisted. This enables:
- Resume after failure — if a worker crashes mid-execution, the agent can resume from last checkpoint rather than restart from scratch
- Human-in-the-loop interrupts — agent pauses at a defined interrupt point, state is persisted, human reviews and approves, execution resumes from exactly where it paused
- Full audit trail — every state checkpoint is queryable; you can replay the entire decision history for any agent thread
- Multi-session continuation — user can close browser, come back tomorrow, and the thread continues from where they left off
Background tasks with Celery for long-running execution
Some agent tasks take minutes, not seconds — processing a 200-page tender document, running a full market intelligence sweep across multiple data sources, or coordinating a multi-step research workflow. Blocking an HTTP request thread for minutes is not acceptable.
The pattern: HTTP endpoint accepts the task, dispatches to Celery, returns a task ID immediately. Client polls /tasks/{task_id}/status or receives updates via SSE.
Authentication and multi-tenancy
For a SaaS AI agent system, every thread_id must be scoped to the authenticated subscriber. A subscriber must not be able to access or influence another subscriber's agent threads.
Deployment on realistic Malaysian SME infrastructure
Not everyone has AWS enterprise contracts or Google Cloud credits. For early-stage Malaysian SaaS products, here's what actually works:
| Component | Recommended setup | Monthly cost (est.) |
|---|---|---|
| Primary VPS | 8 vCPU / 16GB RAM — Hetzner CX52 or DigitalOcean 8vCPU | RM 100–200 |
| PostgreSQL | Same VPS (early stage) or managed DB (scale stage) | RM 0–200 |
| Redis | Same VPS via Docker, or Upstash Redis serverless | RM 0–50 |
| Object storage | Cloudflare R2 (no egress fees — important for Malaysia) | RM 0–20 |
| Reverse proxy | Nginx on same VPS, Cloudflare in front for DDoS & CDN | RM 0 |
| Process manager | supervisord managing Uvicorn workers + Celery workers | RM 0 |
Total infrastructure cost for early-stage: RM 100–470/month. This setup handles 20–30 concurrent agent sessions comfortably. Scale trigger: when you need more than that, split into dedicated database server first, then consider managed Kubernetes if complexity justifies it.
Why Cloudflare R2 for object storage: Standard AWS S3 charges for egress bandwidth — data transferred out to your users. In Malaysia, this can add up significantly for document-heavy agent workflows. R2 charges zero egress fees. For an agent system that regularly processes and serves PDF documents, this is a real cost difference.
What we use and why
FastAPI (>=0.111) + LangGraph (>=0.2) + PostgreSQL 16 + Redis 7 + Celery 5. This is the exact stack IRIS runs on. Version pinning matters — LangGraph's API surface changed significantly between 0.1 and 0.2; don't upgrade without running your full test suite.
Uvicorn behind Nginx, managed by supervisord. Not Gunicorn with Uvicorn workers for this use case — the async nature of agent execution means a single Uvicorn process with proper concurrency handles our load better than the Gunicorn process model. Supervisord handles restarts and log management cleanly without Docker overhead on single-VPS deployments.
We do not use Docker in production for single-VPS deployments. Container overhead adds complexity and memory pressure that isn't worth it at this scale. Direct process management with supervisord is simpler to debug, easier to monitor, and boots faster after VPS restarts. Docker becomes worthwhile when you're orchestrating across multiple servers.
Alembic for database migrations. Every schema change — including LangGraph checkpoint table changes — goes through Alembic migrations. This is non-negotiable for production systems where you can't afford to recreate the database.
Common production failures and how to avoid them
Missing X-Accel-Buffering: no header. SSE streaming works locally, breaks behind Nginx. Add the header. Set proxy_buffering off in your Nginx location block for SSE routes.
LangGraph thread_id collision between tenants. If two subscribers happen to use the same thread_id, their state will mix. Always namespace thread IDs with subscriber ID. f"{subscriber_id}:{user_thread_id}" is the minimum safe pattern.
Celery task timeout with long-running agents. Default Celery task timeout is 30 seconds. Agent workflows that process large documents can run longer. Set task_soft_time_limit and task_time_limit explicitly based on your expected max execution time, and handle SoftTimeLimitExceeded gracefully.
PostgreSQL connection pool exhaustion. Under concurrent load, if each request opens its own connection rather than using the pool, Postgres will hit max_connections. Use psycopg_pool.AsyncConnectionPool with a max_size that's appropriate for your Postgres instance's max_connections setting.
LangGraph version upgrades breaking checkpoints. The checkpoint schema can change between LangGraph versions. Test upgrades on a copy of production data before applying to production. Checkpoint table incompatibility is silent — the agent may start from scratch rather than resume, without throwing an obvious error.
Takeaway
The gap between a working LangGraph notebook and a production AI agent backend is real but bridgeable with the right architecture decisions. FastAPI handles the HTTP layer correctly for async streaming workloads. PostgreSQL gives you the persistent state that production reliability requires. Celery decouples long-running execution from HTTP request cycles.
Start with the simplest version: FastAPI + LangGraph + PostgreSQL on a single VPS. Get it working. Monitor it. Add Celery when you have long-running tasks that need it. Add horizontal scaling when your metrics show you need it. Avoid over-engineering for a scale you don't have yet.
