How Different Agent Types Communicate and Coordinate in the TradingAgents Multi-Agent System

TradingAgents-CN uses a state-driven graph architecture where specialized agents share a centralized AgentState, communicate via message passing, and coordinate through conditional routing logic that dynamically determines the next execution step based on tool-call counters and debate rounds.

The hsliuping/TradingAgents-CN repository implements a modular multi-agent system for financial trading analysis. Understanding how different agent types communicate and coordinate within this multi-agent system in TradingAgents reveals a sophisticated orchestration pattern built on LangGraph's state management primitives.

The State-Driven Graph Architecture

At the core of TradingAgents-CN lies a state-driven graph that strings together specialized agents including market analysts, social-media monitors, news analysts, fundamentals researchers, and risk managers. The architecture decouples agent logic from execution flow through centralized state management.

Centralized State Management with AgentState

All agents communicate through a single shared state object defined in tradingagents/agents/utils/agent_states.py. The AgentState class extends MessagesState and maintains:

  • messages: A chronological list of HumanMessage, AIMessage, and ToolMessage objects accessible to every agent
  • Tool-call counters: Fields like market_tool_call_count and sentiment_tool_call_count prevent infinite loops
  • Debate state: The investment_debate_state dictionary tracks debate rounds between Bull and Bear researchers

# tradingagents/agents/utils/agent_states.py (simplified)

class AgentState(MessagesState):
    market_tool_call_count: int = 0
    sentiment_tool_call_count: int = 0
    investment_debate_state: dict = {}
    # ... additional fields

The TradingAgentsGraph Orchestrator

The TradingAgentsGraph class in tradingagents/graph/trading_graph.py serves as the central orchestrator. Its __init__ method (lines 93-110) initializes LLMs, memory systems, and a Toolkit instance, then delegates graph construction to GraphSetup.

from tradingagents.graph.trading_graph import TradingAgentsGraph

# Build a graph that runs market → social → news → fundamentals

graph = TradingAgentsGraph(
    selected_analysts=["market", "social", "news", "fundamentals"],
    debug=False,
    config=None,
).graph  # compiled StateGraph ready to stream

Agent Communication Mechanisms

Communication in TradingAgents-CN follows a message-passing paradigm where agents read from and write to the shared state rather than invoking each other directly.

Message Passing via AgentState

When an analyst node executes, it receives the current AgentState, processes the messages list through its ChatPromptTemplate, and returns a new message segment. The LangGraph runtime merges this back into the shared state.

In tradingagents/agents/analysts/market_analyst.py (lines 100-118), the market analyst constructs a prompt that explicitly references message history:

prompt = ChatPromptTemplate.from_messages([
    ("system",
     "你是一位专业的股票技术分析师 … "
     "1. 如果消息历史中没有工具结果,立即调用 get_stock_market_data_unified 工具 … "
     "2. … 不要重复调用工具!一次工具调用就足够了!"),
    MessagesPlaceholder(variable_name="messages"),
])
chain = prompt | llm.bind_tools([toolkit.get_stock_market_data_unified])
result = chain.invoke({"messages": state["messages"]})

Tool Invocation and Result Routing

When an LLM decides to invoke a tool, it emits tool_calls in its response. LangGraph routes these to the appropriate ToolNode created in TradingAgentsGraph._create_tool_nodes (lines 20-33 of tradingagents/graph/trading_graph.py).

Each analyst type receives a dedicated tool node (e.g., "tools_market") that bundles its specific data-source tools. Execution results are wrapped as ToolMessage objects and appended to state["messages"], making them available to subsequent agents.

Coordination Through Conditional Logic

The system achieves coordination not through hard-coded sequences but through conditional routing functions that inspect the shared state and dynamically determine the next execution step.

Analyst Routing and Tool-Call Limits

The ConditionalLogic class in tradingagents/graph/conditional_logic.py (lines 18-62) implements routing decisions for each analyst type. Methods like should_continue_market check tool-call counters in the state to prevent infinite loops:


# Conceptual flow from conditional_logic.py

def should_continue_market(self, state: AgentState) -> str:
    # Check if tool has been called

    if state["market_tool_call_count"] < 1 and not has_tool_results(state):
        return "tools_market"  # Route to tool node

    # Check if we need to clear messages before next analyst

    if should_clear_messages(state):
        return "Msg Clear Market"
    # Move to next analyst

    return "next_analyst"

In tradingagents/graph/setup.py (lines 80-104), GraphSetup.setup_graph wires these conditional edges using add_conditional_edges, connecting each analyst to its tool node, message-clear node, and successor.

Researcher Debate Loop Control

The Bull and Bear researchers coordinate through a structured debate loop controlled by ConditionalLogic.should_continue_debate (lines 200-218). This method reads state["investment_debate_state"] to track round counts and current speaker:

def should_continue_debate(self, state: AgentState) -> str:
    current_count = state["investment_debate_state"]["count"]
    max_count = 2 * self.max_debate_rounds
    if current_count >= max_count:
        return "Research Manager"
    # Alternate between researchers based on last speaker

    return "Bear Researcher" if state["investment_debate_state"]["current_response"].startswith("Bull") else "Bull Researcher"

The graph edges in setup.py (lines 99-106) link the two researcher nodes to this logic, creating a bounded back-and-forth conversation that terminates after max_debate_rounds exchanges.

Code Examples

Initializing the Multi-Agent Graph

The following example demonstrates how to instantiate the complete coordination graph with specific analyst types:

from tradingagents.graph.trading_graph import TradingAgentsGraph

# Configure which analysts participate in the workflow

selected_analysts = ["market", "social", "news", "fundamentals"]

# Initialize the graph builder

trading_graph = TradingAgentsGraph(
    selected_analysts=selected_analysts,
    debug=False,
    config=None  # Uses default configuration

)

# Access the compiled LangGraph StateGraph

compiled_graph = trading_graph.graph

# Execute with initial state

result = compiled_graph.invoke({
    "messages": [{"role": "user", "content": "Analyze AAPL stock"}]
})

Creating a Custom Analyst Node

To extend the system with a custom analyst, implement a factory function and register it in the graph setup:


# my_custom_analyst.py

from tradingagents.utils.logging_init import get_logger
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langgraph.prebuilt import ToolNode

logger = get_logger("default")

def create_my_custom_analyst(llm, toolkit):
    """Factory creating a custom analyst node."""
    def node(state):
        prompt = ChatPromptTemplate.from_messages([
            ("system", "You are a custom analyst specializing in sector rotation analysis."),
            MessagesPlaceholder(variable_name="messages")
        ])
        # No external tools for this analyst

        result = (prompt | llm).invoke({"messages": state["messages"]})
        return {
            "messages": [result], 
            "custom_report": result.content
        }
    return node

Registration in tradingagents/graph/setup.py:


# Inside GraphSetup.setup_graph method

if "my_custom" in selected_analysts:
    analyst_nodes["my_custom"] = create_my_custom_analyst(
        self.quick_thinking_llm, 
        self.toolkit
    )
    delete_nodes["my_custom"] = create_msg_delete()
    tool_nodes["my_custom"] = ToolNode([])  # Empty tool node

Conditional Routing Implementation

The following demonstrates how conditional logic inspects state to coordinate workflow transitions:


# tradingagents/graph/conditional_logic.py (conceptual excerpt)

class ConditionalLogic:
    def __init__(self, config):
        self.max_debate_rounds = config.get("max_debate_rounds", 3)
    
    def should_continue_market(self, state: AgentState) -> str:
        """Determine next step for market analyst."""
        # Prevent infinite tool calls

        if state["market_tool_call_count"] < 1:
            if not self._has_tool_results(state, "market"):
                return "tools_market"
        
        # Clear messages before handing to next analyst

        if self._should_clear_context(state):
            return "Msg Clear Market"
            
        return "social_analyst"  # Next analyst in sequence

    
    def should_continue_debate(self, state: AgentState) -> str:
        """Control Bull/Bear researcher debate flow."""
        debate_state = state["investment_debate_state"]
        current_count = debate_state["count"]
        max_count = 2 * self.max_debate_rounds
        
        if current_count >= max_count:
            return "Research Manager"
        
        # Alternate based on last speaker

        last_speaker = debate_state["current_response"]
        return "Bear Researcher" if last_speaker.startswith("Bull") else "Bull Researcher"

Summary

  • State-driven architecture: All agents communicate through a shared AgentState object defined in tradingagents/agents/utils/agent_states.py, eliminating direct coupling between components.
  • Message-based coordination: Agents read from and write to state["messages"], creating a chronological conversation history that every participant can access via ChatPromptTemplate.
  • Conditional routing: The ConditionalLogic class in tradingagents/graph/conditional_logic.py implements data-driven flow control, inspecting tool-call counters and debate states to determine the next execution step.
  • Tool integration: Each analyst type receives dedicated ToolNode instances created in tradingagents/graph/trading_graph.py, with results automatically appended to the shared state as ToolMessage objects.
  • Extensible design: Adding new analyst types requires only a factory function and registration in tradingagents/graph/setup.py, with the conditional logic and state management handling integration automatically.

Frequently Asked Questions

How does TradingAgents prevent infinite loops during tool calls?

The system implements tool-call counters within the shared AgentState. Each analyst type maintains a specific counter (e.g., market_tool_call_count) that increments when tools are invoked. The ConditionalLogic class checks these counters before routing to tool nodes, ensuring analysts like the market analyst in tradingagents/agents/analysts/market_analyst.py adhere to the "single-tool-call" rule enforced in their system prompts.

What determines the order of execution between different analyst types?

Execution order follows a linear sequence defined in tradingagents/graph/setup.py where analyst nodes are added to the state graph. The ConditionalLogic methods (e.g., should_continue_market) explicitly return the next node name (e.g., "social_analyst") after completing the current analyst's work. This creates a deterministic pipeline: market → social → news → fundamentals, though the system supports dynamic reordering through configuration changes.

How do the Bull and Bear researchers coordinate their debate without talking over each other?

The debate coordination relies on the investment_debate_state dictionary stored in AgentState. The ConditionalLogic.should_continue_debate method tracks the current speaker and round count. After each researcher responds, the method inspects state["investment_debate_state"]["current_response"] to determine the next speaker, alternating between Bull and Bear until reaching max_debate_rounds. This state-machine approach ensures structured turn-taking without direct agent-to-agent messaging.

Can I add a new analyst type that uses external APIs without modifying the core graph logic?

Yes, the modular design supports this through the factory pattern used in tradingagents/graph/setup.py. Create a new factory function (e.g., create_custom_analyst) that accepts llm and toolkit parameters, define any required tools in the toolkit, and register the analyst in the selected_analysts conditional block. The existing ConditionalLogic framework and AgentState structure will automatically handle message passing and routing for your new analyst without requiring changes to the core graph orchestration logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →