When building multi-agent systems, developers often fall into the "Chatty Agent" trap. They design agents that pass messages directly to one another in a complex web of peer-to-peer conversations. This quickly leads to infinite loops, context window overflow, and debugging nightmares.
Enter the Blackboard Architecture.
Originating from early AI speech recognition systems in the 1970s, the Blackboard pattern is a centralized, event-driven design. Instead of agents talking to each other, they communicate exclusively through a shared, global data structure—the Blackboard.
In the context of LangGraph, this pattern maps perfectly:
The Blackboard = The Graph
State.The Knowledge Sources (Agents) = The Graph
Nodes.The Controller = The
Conditional Edges/ Supervisor Router.
In this end-to-end guide, we will implement a Blackboard Architecture for a highly complex Enterprise Bank Policy RAG System, demonstrating how centralized state management creates resilient, observable, and dynamic AI workflows.
The Real-World Use Case: TechBank "Loan Exception" Committee
Imagine TechBank receives a commercial loan application for $10M. The automated underwriting system flags a Policy Exception: the borrower's Debt-Service Coverage Ratio (DSCR) is 1.15x, but the bank policy strictly requires 1.25x.
However, the borrower has $5M in liquid cash reserves. Can the loan be approved?
To answer this, we don't use a single LLM. We convene a virtual "Loan Exception Committee" using the Blackboard pattern. The committee consists of specialized agents:
Credit Risk Agent: Evaluates the financials and DSCR.
Compliance Agent: Checks for AML/KYC flags and regulatory constraints.
Collateral Agent: Assesses the liquidity and value of the pledged assets.
Executive Synthesizer: Reviews the blackboard and makes the final decision.
Why Blackboard?
If the Compliance Agent finds a severe Anti-Money Laundering (AML) red flag, it writes this to the Blackboard. The Controller reads the Blackboard, sees the AML flag, and dynamically routes the workflow to an immediate "Auto-Reject" node, skipping the Collateral Agent entirely. This dynamic, state-driven routing is the superpower of the Blackboard pattern.

Technology Stack
| Component | Technology | Role in Architecture |
|---|---|---|
| Orchestration | LangGraph | Implements the Blackboard (State) and Controller (Routing). |
| LLM Provider | Azure OpenAI (GPT-4o) | Powers the reasoning of the Knowledge Source agents. |
| Vector Database | pgvector (PostgreSQL) | Stores the bank's policy manuals for RAG retrieval. |
| State Management | Pydantic / TypedDict | Defines the strict schema of the Blackboard. |
| Observability | LangSmith | Traces the Blackboard updates for regulatory auditing. |
End-to-End Implementation
Let's build this system. We will explicitly design the State as a Blackboard, where agents only read from and write to specific fields, never passing data directly to each other.
Step 1: Define the Blackboard (The State)
The State is the heart of the Blackboard architecture. It must hold the initial data, the intermediate findings from each agent, and the conversation memory.
from typing import TypedDict, List, Annotated, Optionalimport operator
from langgraph.graph import StateGraph, END
class LoanExceptionState(TypedDict):
# 1. The Shared Memory (Conversation/Action History)
messages: Annotated[List[str], operator.add]
# 2. The Initial Input (Read by all agents)
loan_application: dict
# 3. The Blackboard Fields (Written by specific agents, read by others)
credit_assessment: Optional[str]
compliance_check: Optional[str]
collateral_valuation: Optional[str]
# 4. The Final Output
final_decision: Optional[str]
# 5. Controller Metadata
current_phase: str
is_rejected: bool # Used for dynamic short-circuitingStep 2: Build the Knowledge Sources (The Agents)
Each agent acts as a "Knowledge Source." It reads the Blackboard, performs its specialized RAG task, and writes only to its designated field.
Node 1: Credit Risk Agent
def credit_risk_agent(state: LoanExceptionState) -> LoanExceptionState:
print("📊 [Credit Agent] Analyzing financials and retrieving DSCR policy...")
# Simulating RAG: Retrieving policy on DSCR exceptions
policy_snippet = "Policy 4.2: DSCR < 1.25x requires compensating factors (e.g., >$3M liquid reserves)."
# Agent reasoning (simulated)
assessment = f"Initial DSCR is 1.15x (Fail). However, borrower has $5M liquid reserves. Meets compensating factor criteria per: '{policy_snippet}'"
return {
"credit_assessment": assessment,
"messages": ["Credit Agent: Financial analysis complete. Compensating factors identified."],
"current_phase": "credit_done"
}
Node 2: Compliance Agent
def compliance_agent(state: LoanExceptionState) -> LoanExceptionState:
print("🛡️ [Compliance Agent] Running AML/KYC checks against global watchlists...")
# Simulating a critical failure to demonstrate dynamic routing
# In a real scenario, this would query an external API or RAG policy
is_aml_clear = False
if not is_aml_clear:
return {
"compliance_check": "CRITICAL: Borrower entity matched on OFAC secondary sanctions list.",
"messages": ["Compliance Agent: CRITICAL AML FLAG DETECTED."],
"is_rejected": True, # Signals the controller to short-circuit
"current_phase": "compliance_failed"
}
return {
"compliance_check": "KYC and AML checks passed. No regulatory blockers.",
"messages": ["Compliance Agent: Regulatory checks passed."],
"current_phase": "compliance_done"
}
Node 3: Collateral Agent
def collateral_agent(state: LoanExceptionState) -> LoanExceptionState:
print("🏢 [Collateral Agent] Evaluating asset liquidity and LTV...")
# Simulating RAG retrieval for collateral policy
valuation = "Appraised value is $12M. Loan-to-Value (LTV) is 83%. Policy requires max 85% for commercial real estate."
return {
"collateral_valuation": valuation,
"messages": ["Collateral Agent: Asset valuation complete. LTV is within policy limits."],
"current_phase": "collateral_done"
}
Node 4: Executive Synthesizer
def executive_synthesizer(state: LoanExceptionState) -> LoanExceptionState:
print("👔 [Executive] Synthesizing committee findings...")
# The synthesizer reads the entire blackboard to make a decision
decision = f"""
LOAN EXCEPTION COMMITTEE DECISION:
----------------------------------
Credit: {state['credit_assessment']}
Compliance: {state['compliance_check']}
Collateral: {state['collateral_valuation']}
FINAL RULING: CONDITIONAL APPROVAL. The DSCR exception is mitigated by liquid reserves, and collateral is sufficient.
"""
return {
"final_decision": decision,
"messages": ["Executive: Final decision reached."],
"current_phase": "complete"
}
Step 3: Build the Controller (Dynamic Routing)
This is where the Blackboard pattern shines. The Controller doesn't follow a rigid sequence; it looks at the Blackboard and decides what happens next. If the Compliance Agent flags an AML issue, the Controller skips the Collateral Agent and goes straight to rejection.
def blackboard_controller(state: LoanExceptionState) -> str:
"""
The Controller monitors the Blackboard and routes the workflow.
"""
# 1. Check for short-circuit conditions (Dynamic Routing)
if state.get("is_rejected"):
print("🛑 [Controller] Critical compliance flag detected. Short-circuiting to rejection.")
return "auto_reject"
# 2. Check what is missing on the Blackboard and route accordingly
if not state.get("credit_assessment"):
return "credit_risk"
elif not state.get("compliance_check"):
return "compliance"
elif not state.get("collateral_valuation"):
return "collateral"
elif not state.get("final_decision"):
return "executive"
return "end"
def auto_reject_node(state: LoanExceptionState) -> LoanExceptionState:
print("❌ [System] Auto-rejecting loan due to compliance failure.")
return {
"final_decision": "REJECTED: Loan application denied due to critical AML/Compliance flags.",
"messages": ["System: Auto-rejected."],
"current_phase": "rejected"
}
Step 4: Compile the Graph
Now we wire the Blackboard, the Knowledge Sources, and the Controller together.
def build_blackboard_graph():
workflow = StateGraph(LoanExceptionState)
# Add Knowledge Sources (Nodes)
workflow.add_node("credit_risk", credit_risk_agent)
workflow.add_node("compliance", compliance_agent)
workflow.add_node("collateral", collateral_agent)
workflow.add_node("executive", executive_synthesizer)
workflow.add_node("auto_reject", auto_reject_node)
# The Controller is the single source of truth for routing
# Every node routes back to the controller, which reads the blackboard
workflow.set_entry_point("credit_risk") # Start with credit
workflow.add_conditional_edges("credit_risk", blackboard_controller, {
"compliance": "compliance",
"auto_reject": "auto_reject",
"end": END
})
workflow.add_conditional_edges("compliance", blackboard_controller, {
"collateral": "collateral",
"executive": "executive",
"auto_reject": "auto_reject",
"end": END
})
workflow.add_conditional_edges("collateral", blackboard_controller, {
"executive": "executive",
"end": END
})
workflow.add_conditional_edges("executive", blackboard_controller, {
"end": END
})
workflow.add_edge("auto_reject", END)
return workflow.compile()
app = build_blackboard_graph()
Running the System
Let's run the workflow. Because our simulated Compliance Agent found an AML flag, watch how the Controller dynamically alters the flow.
initial_state = {
"messages": [],
"loan_application": {"amount": 10000000, "dscr": 1.15, "reserves": 5000000},
"credit_assessment": None,
"compliance_check": None,
"collateral_valuation": None,
"final_decision": None,
"current_phase": "start",
"is_rejected": False
}
result = app.invoke(initial_state)
print("\n--- Blackboard Action Log ---")
for msg in result["messages"]:
print(f"• {msg}")
print("\n--- Final Blackboard Output ---")
print(result["final_decision"])
Output Trace
📊 [Credit Agent] Analyzing financials and retrieving DSCR policy...
🛡️ [Compliance Agent] Running AML/KYC checks against global watchlists...
🛑 [Controller] Critical compliance flag detected. Short-circuiting to rejection.
❌ [System] Auto-rejecting loan due to compliance failure.
--- Blackboard Action Log ---
• Credit Agent: Financial analysis complete. Compensating factors identified.
• Compliance Agent: CRITICAL AML FLAG DETECTED.
• System: Auto-rejected.
--- Final Blackboard Output ---
REJECTED: Loan application denied due to critical AML/Compliance flags.
Notice that the Collateral Agent and Executive Synthesizer were never executed. The Blackboard Controller saw the is_rejected flag and saved the bank thousands of tokens and seconds of latency by short-circuiting the workflow.
Enterprise Best Practices for Blackboard Architecture
Strict Schema Enforcement: Use Pydantic models for your Blackboard state. If an agent tries to write to a field it doesn't own, the type checker should catch it. This prevents "state pollution."
Avoid State Bloat: The Blackboard holds everything. If agents write massive blocks of text (like full RAG document dumps) into the state, the context window will overflow. Instruct agents to write summaries or extractions to the Blackboard, not raw data.
Use LangSmith for Blackboard Auditing: In banking, you must prove why a decision was made. LangSmith allows you to visualize the Blackboard at every step. You can literally see the
compliance_checkfield change fromnullto"CRITICAL FLAG", providing a perfect audit trail.Decouple Agents from Routing: Notice how the
credit_risk_agentdoesn't know thecompliance_agentexists. It just writes to the Blackboard. This makes your agents highly modular. You can swap the Compliance Agent for a new version without rewriting the Credit Agent.
Conclusion
The Blackboard Architecture is the ultimate antidote to the chaos of "chatty" multi-agent systems. By centralizing state, decoupling agents, and introducing a dynamic Controller, you create a system that is not only highly efficient but also inherently auditable.
For TechBank, this means their Loan Exception Committee doesn't just process loans faster; it processes them with strict adherence to policy, dynamically adapting to risks in real-time, all while maintaining a perfect, observable trail of every decision made.

Join the conversation! Your thoughts help the community grow.