Introduction
In enterprise AI deployments, we often focus on the intelligence of the agents, but we neglect their infrastructure footprint. When an LLM agent enters a reasoning loop, hallucinates a bad tool argument, or crashes mid-execution, what happens to the resources it allocated?
If an agent opens a database connection to query a live ledger and crashes before closing it, that connection leaks. A few bad LLM loops will exhaust your database connection pool, bringing down the entire enterprise application. Similarly, if an agent allocates GPU memory for a local embedding model and fails to release it, you trigger an Out-Of-Memory (OOM) crash.
The solution is the Custom Context Manager. By wrapping resource allocation in Python’s __enter__ and __exit__ protocols, we guarantee that infrastructure resources are cleaned up regardless of how the agent fails.
In this article, we will build a thread-safe Database Connection Pool & Transaction Context Manager. We will then integrate it into an end-to-end FinTech Audit Multi-Agent LangGraph System, demonstrating how strict resource management combines with conversational memory and state to create a resilient, enterprise-grade AI.
Part 1: The Custom Context Manager
To manage database connections safely, we need a mechanism that acquires a connection from a pool, executes a transaction, and guarantees the connection is returned to the pool—even if an exception occurs.
While Python’s @contextlib.contextmanager is useful for simple tasks, a class-based context manager is superior for complex state management like connection pooling, as it explicitly tracks the resource lifecycle.
import threading
from typing import Any
# --- 1. Simulated Database Infrastructure ---
class SimulatedConnection:
def __init__(self, conn_id: int):
self.conn_id = conn_id
self.autocommit = False
def execute(self, query: str):
print(f" [DB Conn {self.conn_id}] Executing: {query}")
def commit(self):
print(f" [DB Conn {self.conn_id}] Transaction COMMITTED.")
def rollback(self):
print(f" [DB Conn {self.conn_id}] Transaction ROLLED BACK.")
class SimulatedConnectionPool:
def __init__(self, max_size: int = 3):
self._available = [SimulatedConnection(i) for i in range(max_size)]
self._lock = threading.Lock()
def getconn(self) -> SimulatedConnection:
with self._lock:
if not self._available:
raise ConnectionError("Database connection pool exhausted!")
return self._available.pop()
def putconn(self, conn: SimulatedConnection):
with self._lock:
self._available.append(conn)
# --- 2. The Custom Context Manager ---
class DBTransactionContext:
"""
A custom context manager that acquires a DB connection from a pool,
manages the transaction lifecycle, and guarantees the connection is
returned to the pool even if the agent crashes.
"""
def __init__(self, pool: SimulatedConnectionPool):
self.pool = pool
self.conn: SimulatedConnection = None
def __enter__(self) -> SimulatedConnection:
# 1. Acquire resource
self.conn = self.pool.getconn()
self.conn.autocommit = False
print(f" [Context Manager] Acquired connection {self.conn.conn_id} from pool.")
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
# 2. Manage transaction based on success/failure
if exc_type is not None:
print(f" [Context Manager] Exception detected ({exc_type.__name__}). Initiating ROLLBACK.")
self.conn.rollback()
else:
self.conn.commit()
# 3. Guarantee resource release
self.pool.putconn(self.conn)
print(f" [Context Manager] Returned connection {self.conn.conn_id} to pool.")
# Return False to propagate the exception to LangGraph's ToolNode
return False
Why this matters
The __exit__ method is the enterprise shield. It intercepts any exception raised inside the with block, forces a database rollback to prevent partial/corrupt writes, and always returns the connection to the pool.
Part 2: The Enterprise Use Case
The Scenario: A FinTech Compliance Command Center
A compliance officer asks:
"Query the live transaction ledger for suspicious transfers over $10,000 today, and log this audit action in our compliance database."
The Workflow
Agent 1 (Compliance Analyst): Writes the SQL query and executes it against the live ledger. It must also write an audit log.
Agent 2 (RAG Synthesizer): Takes the ledger results, queries the Vector DB for historical compliance policies, and generates a final report.
The Risk
If the Analyst agent hallucinates a bad SQL query, the database will throw an error. Without a context manager, the connection would hang, and the audit log would be partially written. With our context manager, the transaction rolls back cleanly, and the connection is freed.
Part 3: The Multi-Agent LangGraph Implementation
We will use LangGraph to orchestrate this flow. We must distinguish between three types of "memory" in this architecture:
Conversational Memory: LangGraph's
MemorySavertracks the chat history.Structural State: Pydantic models track the data payload (ledger results) between nodes.
Infrastructure Memory: Our Context Manager tracks DB connections, ensuring they don't leak.
1. Defining the State and Tools
import os
import getpass
from typing import TypedDict, Annotated, Sequence, List, Dict, Any
from pydantic import BaseModel
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import ToolNode
# Initialize the global DB Pool (In reality, this is initialized at app startup)
db_pool = SimulatedConnectionPool(max_size=3)
# --- 1. Strict State Definitions ---
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], "add_messages"]
ledger_results: List[Dict[str, Any]]
audit_status: str
# --- 2. Define the Tools ---
@tool
def query_financial_ledger(sql_query: str, simulate_error: bool = False) -> str:
"""
Executes a SQL query against the live financial ledger and logs the audit.
WARNING: Ensure SQL is valid. Invalid SQL will trigger a transaction rollback.
"""
print(" [TOOL] Opening DB Transaction Context...")
# THE MAGIC: The context manager guarantees cleanup
with DBTransactionContext(db_pool) as conn:
conn.execute(sql_query)
# Simulate the LLM generating a bad query to test our context manager
if simulate_error:
raise ValueError("Simulated SQL Syntax Error: relation 'users' does not exist")
conn.execute("INSERT INTO audit_log (action, status) VALUES ('LEDGER_QUERY', 'SUCCESS')")
return "Query executed successfully. Audit log updated."
tools = [query_financial_ledger]

Join the conversation! Your thoughts help the community grow.