**Building a Production-Ready Booking Agent with LangGraph and PostgreSQL**
In this article, we continue building on a stateful LangGraph agent originally designed to handle a 15-minute booking process, now enhanced with a persistent PostgreSQL backend. Initially, the agent operated entirely in memory, orchestrating key steps such as responding to customer queries, calculating prices, handling acceptance or rejection, proposing optimized time slots, and confirming appointments—all within a Streamlit user interface.
While the in-memory implementation was useful for prototyping and demonstration purposes, it lacked the reliability and scalability required for a real-world product. To transition from demo to production, we needed a durable database layer capable of supporting multiple user sessions, preserving conversation state, and preventing double-booking scenarios.
This led to the adoption of **PostgreSQL** as a robust, open-source relational database, enabling structured storage of both **technician information** and **booking records**. By introducing a repository pattern, the system can now switch between `PostgresBookingRepository` and `InMemoryBookingRepository` through a stable interface, making the architecture flexible and testable.
—
### What the Database Looks Like Now
Previously, data was stored using two in-memory Python objects:
– A **LangGraph checkpointer** using `MemorySaver()` to preserve conversation state across turns.
– A thread-safe Python list acting as an **in-memory booking repository** to track confirmed appointments.
This structure worked for demos, but suffered from critical limitations:
– Data disappeared when the process restarted.
– Each session maintained its own isolated calendar.
– The agent could overbook slots because the in-memory view quickly became stale.
In contrast, the new PostgreSQL-backed system ensures that conversation checkpoints and booking data survive restarts and remain consistent across all interfaces.
—
### Why We Need a Proper Database
An in-memory database is suitable for unit testing and quick demos, but it cannot support a production-grade product. Without persistence:
– Conversations and bookings vanish on restart.
– Multiple users cannot share the same calendar.
– The risk of double-booking increases due to stale data.
PostgreSQL solves these issues by providing:
– Durable storage of conversation states.
– Shared access to booking data across multiple sessions and interfaces.
– Reliable transactions to maintain integrity during concurrent bookings.
—
### PostgreSQL Implementation Overview
To support PostgreSQL, we introduced a **BookingRepository Protocol**, defining a stable interface for persistence operations:
– Listing existing bookings.
– Creating new bookings with overlap checks.
– Accessing technician details.
This allowed us to implement two interchangeable backends:
– `InMemoryBookingRepository` for testing and demos.
– `PostgresBookingRepository` for production.
At application startup, a `create_persistence()` function chooses the appropriate backend based on the presence of a `DATABASE_URL`. Both the repository and LangGraph checkpointer use the same persistence layer, ensuring consistency.
—
### Database Interactions in the Booking Workflow
The updated agent architecture follows this flow:
1. **Generate schedule options** – Reads existing bookings and technician data to propose optimal slots.
2. **Select a slot** – Updates agent state with the user’s choice.
3. **Confirm booking** – Writes the confirmed booking to the database via the repository.
4. **Persist conversation** – The checkpointer saves the updated agent state across turns.
Only two nodes interact directly with the booking repository:
– The schedule generation node (read).
– The confirmation node (write).
All other nodes work exclusively with `AgentState`, which serves as the working memory of the graph.
—
### Code Highlights
The repository pattern in action:
“`python
repository = repository or InMemoryBookingRepository()
“`
Confirming a booking using the repository:
“`python
booking = repository.create_booking(
option, state[“booking_details”], float(state[“calculated_price”])
)
“`
The `AgentState` definition:
“`python
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
booking_details: BookingDetails
calculated_price: NotRequired[float | None]
time_options: NotRequired[list[TimeOption]]
selected_slot: NotRequired[TimeOption | None]
status: BookingStatus
booking_id: NotRequired[str | None]
“`
—
### FAQ
**Q: Can I still use the in-memory version?**
Yes. If no `DATABASE_URL` is set, the application defaults to in-memory mode, which is ideal for local testing and development.
**Q: How does double-booking get prevented?**
The `create_booking` method in `PostgresBookingRepository` checks for overlapping bookings before inserting a new record, ensuring slot availability.
**Q: What happens to my conversation if I restart the app?**
With PostgreSQL configured, your conversation checkpoints and bookings persist across restarts. In memory mode, all data is lost.
**Q: Can I add more booking interfaces later?**
Absolutely. Because the backend uses a shared PostgreSQL database, you can easily integrate additional frontends such as WhatsApp, Telegram, or a custom web app.
—
### Conclusion
By introducing a PostgreSQL backend, we transformed a basic LangGraph demo into a scalable and production-ready booking system. The agent now reliably handles real business requirements, including persistent state management, accurate scheduling, and multi-session availability.
This architecture provides a solid foundation for future enhancements, such as integration with other communication channels, advanced scheduling rules, and monitoring tools. If you’d like to see the full implementation, the source code is available on GitHub in the `customer-service-agent` repository. Feel free to clone, test, and extend it for your own use cases.
Thank you for reading.



