DEV Community

Cover image for ๐…๐š๐ฌ๐ญ๐€๐๐ˆ + ๐‹๐š๐ง๐ ๐†๐ซ๐š๐ฉ๐ก: ๐–๐ก๐š๐ญ ๐š ๐‚๐ฅ๐ข๐ž๐ง๐ญ ๐ƒ๐ข๐ฌ๐œ๐จ๐ง๐ง๐ž๐œ๐ญ ๐‚๐จ๐ฆ๐ฆ๐ข๐ญ๐ฌ
Ranjan Kumar
Ranjan Kumar

Posted on

๐…๐š๐ฌ๐ญ๐€๐๐ˆ + ๐‹๐š๐ง๐ ๐†๐ซ๐š๐ฉ๐ก: ๐–๐ก๐š๐ญ ๐š ๐‚๐ฅ๐ข๐ž๐ง๐ญ ๐ƒ๐ข๐ฌ๐œ๐จ๐ง๐ง๐ž๐œ๐ญ ๐‚๐จ๐ฆ๐ฆ๐ข๐ญ๐ฌ

Your handler's shape decides whether an interrupted agent run stops, finishes, or half-completes in the shadows. Not your checkpointer.

I wired a four-node LangGraph agent to a real uvicorn server, closed the client connection six hundred milliseconds in - after charge ran but before notify started. The thread stored next=['notify']. Then a user came back on the same thread_id and the graph ran again from the start. charge fired twice. One duplicate sat visible in the transcript. Change one setting and that duplicate vanishes completely - which is worse.

No crash. No worker death. No region failover. Just someone closing a connection. But the side effect already executed with no durable record of it.

๐‡๐š๐ง๐๐ฅ๐ž๐ซ-๐’๐ก๐š๐ฉ๐ž๐ ๐ƒ๐ฎ๐ซ๐š๐›๐ข๐ฅ๐ข๐ญ๐ฒ: ๐ญ๐ก๐ž ๐œ๐ฅ๐š๐ข๐ฆ

In a self-hosted FastAPI service, your request handler's structure determines whether an interrupted agent run stops, completes, or half-completes. Three handler shapes - all passing code review, all serving the same response - produce three different durable outcomes from one disconnect. It is not your checkpointer. It is not your durability setting. It is the handler.

๐–๐ก๐ฒ ๐ญ๐ก๐ข๐ฌ ๐ฆ๐š๐ญ๐ญ๐ž๐ซ๐ฌ ๐ข๐ง ๐ฉ๐ซ๐จ๐๐ฎ๐œ๐ญ๐ข๐จ๐ง

The disconnect path is not rare. Closed laptop. Mobile handoff. Proxy idle timeout at 60 seconds. A user who lost interest. In a consumer-facing agent it runs at volume and drives your system down the code path you built for crash recovery and have probably never tested. The "transport gap" is real: HTTP streaming binds the connection, the device, and the server to one request. A drop on either side ends the session with no way to resume - and no one flags what already committed on the way out.

๐–๐ก๐š๐ญ ๐ฒ๐จ๐ฎ ๐ง๐ž๐ž๐ ๐ญ๐จ ๐๐ž๐œ๐ข๐๐ž ๐ง๐จ๐ฐ

Do you need your agent's side effects to be idempotent? Do you need to detect duplicates after the fact? Do you need to prevent them altogether? Your answer shapes whether you use a detached handler (fire-and-forget, no visibility), an attached handler (blocks the response, highest safety), or a hybrid with explicit concurrency control. None of these choices live in your checkpointer. They live in your handler.

Read the full analysis with measurements on real PostgreSQL checkpoints, the competing handler shapes side-by-side, and what each one actually commits:

https://ranjankumar.in/fastapi-langgraph-client-disconnect-durability

Follow for more practitioner-grounded AI systems writing.

FastAPI #LangGraph #DurableExecution #AgenticAI #Checkpointing #ProductionDeployment #AIEngineering

Top comments (0)