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

> Explore how TradingAgents-CN agents communicate and coordinate using a state-driven graph, message passing, and conditional routing for dynamic execution in multi-agent trading.

- Repository: [hsliuping/TradingAgents-CN](https://github.com/hsliuping/tradingagents-cn)
- Tags: internals
- Published: 2026-02-16

---

**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](https://github.com/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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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

```python

# 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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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`.

```python
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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/tradingagents/agents/analysts/market_analyst.py) (lines 100-118), the market analyst constructs a prompt that explicitly references message history:

```python
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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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:

```python

# 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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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:

```python
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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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:

```python
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:

```python

# 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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/tradingagents/graph/setup.py):

```python

# 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:

```python

# 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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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`](https://github.com/hsliuping/TradingAgents-CN/blob/main/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.