Explaining the Blackboard design pattern in LangGraph
Blackboard
If the problem is always the same the previous architectures might have worked …
but what if the best next step depends on the results of the previous one? A rigid sequence can be incredibly inefficient, forcing the system to run unnecessary steps.
This is where the Blackboard architecture comes in. It’s a more advanced and flexible way to coordinate a team of specialists. The idea comes from how human experts solve problems,
- they gather around a blackboard, a shared workspace where anyone can write down a finding.
- A leader then looks at the board and decides who should contribute next.
When you are coding your architecture, Blackboard is your pattern for tackling complex, ill-structured problems where the solution path isn’t known in advance. It allows for an emergent, opportunistic strategy, making it perfect for dynamic sense-making or complex diagnostics where the next step is always a reaction to the latest discovery.
Let’s understand the flow of it..

- Shared Memory (The Blackboard): A central data store holds the current state of the problem and all findings so far.
- Specialist Agents: A pool of independent agents, each with a specific skill, monitors the blackboard.
- Controller: A central ‘controller’ agent also monitors the blackboard. Its job is to analyze the current state and decide which specialist is best equipped to make the next move.
- Opportunistic Activation: The Controller activates the chosen agent. The agent reads from the blackboard, does its work, and writes its findings back.
- Iteration: This process repeats, with the Controller dynamically choosing the next agent, until it decides the problem is solved.
Let’s start building it.
The most important part of this system is the intelligent Controller. Unlike our previous Meta-Controller which just did a one-time dispatch, this one runs in a loop. After every specialist agent runs, the Controller re-evaluates the blackboard and decides what to do next.
class BlackboardState(TypedDict):
user_request: str
blackboard: List[str] # The shared workspace
available_agents: List[str]
next_agent: Optional[str] # The controller's decision
class ControllerDecision(BaseModel):
next_agent: str = Field(description="The name of the next agent to call. Must be one of ['News Analyst', 'Technical Analyst', 'Financial Analyst', 'Report Writer'] or 'FINISH'.")
reasoning: str = Field(description="A brief reason for choosing the next agent.")
def controller_node(state: BlackboardState):
"""The intelligent controller that analyzes the blackboard and decides the next step."""
console.print("--- CONTROLLER: Analyzing blackboard... ---")
controller_llm = llm.with_structured_output(ControllerDecision)
blackboard_content = "\n\n".join(state['blackboard'])
prompt = f"""You are the central controller of a multi-agent system. Your job is to analyze the shared blackboard and the original user request to decide which specialist agent should run next.
**Original User Request:**
{state['user_request']}
**Current Blackboard Content:**
---
{blackboard_content if blackboard_content else "The blackboard is currently empty."}
---
**Available Specialist Agents:**
{', '.join(state['available_agents'])}
**Your Task:**
1. Read the user request and the current blackboard content carefully.
2. Determine what the *next logical step* is to move closer to a complete answer.
3. Choose the single best agent to perform that step.
4. If the request has been fully addressed, choose 'FINISH'.
Provide your decision in the required format.
"""
decision = controller_llm.invoke(prompt)
console.print(f"--- CONTROLLER: Decision is to call '{decision.next_agent}'. Reason: {decision.reasoning} ---")
return {"next_agent": decision.next_agent}
Now we just need to wire it up in LangGraph. The key is the central loop: any specialist agent, after running, sends control back to the Controller for the next decision.
# ... (specialist nodes are defined similarly to the multi-agent system) ...
bb_graph_builder = StateGraph(BlackboardState)
bb_graph_builder.add_node("Controller", controller_node)
bb_graph_builder.add_node("News Analyst", news_analyst_bb)
# ... add other specialist nodes ...
bb_graph_builder.set_entry_point("Controller")
def route_to_agent(state: BlackboardState):
return state["next_agent"]
bb_graph_builder.add_conditional_edges("Controller", route_to_agent, {
"News Analyst": "News Analyst",
# ... other routes ...
"FINISH": END
})
# After any specialist runs, control always returns to the Controller
bb_graph_builder.add_edge("News Analyst", "Controller")
# ... other edges back to controller ...
blackboard_app = bb_graph_builder.compile()
To see why this is so much better than a rigid sequence, let’s give it a task with conditional logic that the sequential agent would fail.
dynamic_query = "Find the latest major news about Nvidia. Based on the sentiment of that news, conduct either a technical analysis (if the news is neutral or positive) or a financial analysis (if the news is negative)."
initial_bb_input = {"user_request": dynamic_query, "blackboard": [], "available_agents": ["News Analyst", "Technical Analyst", "Financial Analyst", "Report Writer"]}
final_bb_output = blackboard_app.invoke(initial_bb_input, {"recursion_limit": 10})
console.print("\n--- [bold green]Final Report from Blackboard System[/bold green] ---")
console.print(Markdown(final_bb_output['blackboard'][-1]))
--- CONTROLLER: Analyzing blackboard... ---
Decision: call 'News Analyst'...
--- Blackboard State ---
AGENT 'News Analyst' is working...
--- CONTROLLER: Analyzing blackboard... ---
Decision: call 'Technical Analyst' (news positive)...
--- Blackboard State ---
Report 1: Nvidia news positive, new AI chip "Rubin", bullish market sentiment...
--- (Blackboard) AGENT 'Technical Analyst' is working... ---
--- CONTROLLER: Analyzing blackboard... ---
Decision: call 'Report Writer' (analysis complete, synthesize report)...
...
The execution trace shows a far more intelligent process. The sequential agent would have run both the Technical and Financial analysts, wasting resources. Our Blackboard system is smarter:
- Controller Start: It sees an empty board and calls the
News Analyst. - News Analyst Runs: It finds positive news about Nvidia and posts it to the board.
- Controller Re-evaluates: It reads the positive news and correctly decides the next step is to call the
Technical Analyst, completely skipping the financial one. - Specialist Runs: The technical analyst does its work and posts its report.
- Controller Finishes: It sees all the necessary analysis is done and calls the
Report Writerto synthesize the final answer before finishing.
This dynamic, opportunistic workflow is exactly what defines a Blackboard system. To make it formal, our LLM-as-a-Judge evaluates each contribution, scoring for logical consistency and efficiency, ensuring that emergent solutions are both sound and actionable.