When multiple agents operate simultaneously on trading operations, they must share a consistent understanding of the world state. This post explores the coordination patterns that make multi-agent trading systems reliable: persistent state management, conflict resolution, and multi-agent RAG for comprehensive analysis.
State Management in Multi-Agent Systems
Imagine a customer service agent who has amnesia - every time you talk to them, you have to start the conversation from scratch. A single-turn agent without memory is like that. To handle complex, multi-step tasks, our agents need a memory - what we call state.
State management is the process of recording what has happened, what the current status is, and what information has been gathered, so that any agent on the team can get up to speed.
Ephemeral vs Persistent State
flowchart TB
subgraph Ephemeral["Ephemeral State"]
direction LR
E1[Chat History] --> E2[Session Context]
E2 --> E3[Lost on Restart]
end
subgraph Persistent["Persistent State"]
direction LR
P1[Database/Files] --> P2[Survives Restarts]
P2 --> P3[Shared Access]
end
classDef pinkClass fill:#E74C3C,stroke:#333,stroke-width:2px,color:#fff
classDef greenClass fill:#27AE60,stroke:#333,stroke-width:2px,color:#fff
class Ephemeral pinkClass
class Persistent greenClass
Type
Duration
Use Case
Ephemeral
Single session
Chat history, temporary calculations
Persistent
Cross-session
Account balances, portfolio holdings, transaction history
Persistent State with Interchangeable Managers
For trading systems, persistent state is essential. The pattern that makes this maintainable is an interchangeable state manager that abstracts storage details:
defget_synchronized_state(self) -> Tuple[float, Dict[str, int]]: # This is where locking mechanisms would be implemented returnself._read_cash_balance(), self._read_portfolio()
The key insight: agents don’t care what type of state manager they use. You could swap CSVStateManager for SQLStateManager without changing agent code.
Agent Separation: Decision vs Execution
A robust trading system separates decision-making from execution. This separation provides:
Clear responsibilities: One decides, one executes
Different characteristics: Decision is non-deterministic (LLM reasoning), execution is deterministic (follows rules)
Testable components: Each can be tested independently
flowchart LR
subgraph Decision["Trade Decision Agent"]
D1[Read Portfolio] --> D2[Analyze State]
D2 --> D3[Make Decision]
end
subgraph Execution["Execution Agent"]
E1[Verify Funds] --> E2[Execute Trade]
E2 --> E3[Update State]
E3 --> E4[Record Transaction]
end
D3 --> |Trade Request| E1
E4 --> |Audit Trail| DB[(Shared State)]
D1 --> |Read| DB
classDef blueClass fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff
classDef orangeClass fill:#F39C12,stroke:#333,stroke-width:2px,color:#fff
class Decision blueClass
class Execution orangeClass
@decision_agent.system_prompt defdecision_prompt(ctx: RunContext[AgentContext]) -> str: return"""You are a Trade Decision Agent. Your role: 1. Analyze portfolio state from storage 2. Read account balance 3. Analyze current holdings 4. Decide if trade should be made 5. Provide clear reasoning You have READ-ONLY access to state."""
@execution_agent.system_prompt defexecution_prompt(ctx: RunContext[AgentContext]) -> str: return"""You are an Execution Agent. Your role: 1. Verify funds/shares availability 2. Execute trades 3. Update state atomically 4. Handle errors gracefully You have WRITE access to state."""
defcreate_state_snapshot(self) -> StateSnapshot: """Create snapshot with version hash for conflict detection""" cash, portfolio = self.state_manager.get_synchronized_state()
# Step 4: Resolve conflicts if needed conflicts_resolved = False if conflicts and all_approved: resolver = ConflictResolver() conflicts_resolved, _ = resolver.resolve( conflicts, trade, self.state_coordinator.state_manager ) ifnot conflicts_resolved: all_approved = False
# Step 5: Execute if approved execution_allowed = all_approved and (not conflicts or conflicts_resolved)
if execution_allowed: self.state_coordinator.state_manager.execute_atomic_trade(trade)
return SupervisorDecision( trade_id=f"TRD_{int(time.time())}", final_decision="APPROVED"if execution_allowed else"REJECTED", reasoning=self._aggregate_reasoning(compliance, risk, account), agent_approvals=agent_approvals, conflicts_detected=[c.value for c in conflicts], conflicts_resolved=conflicts_resolved, execution_allowed=execution_allowed )
Fallback Mechanisms
Critical for production systems - always handle agent failures:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
defrun_compliance_check(self, trade: TradeDecision) -> AgentDecision: """Run compliance check with fallback""" try: result = self.compliance_agent.run_sync( f"Check compliance for {trade.transaction_type}{trade.quantity}{trade.symbol}", deps=self.context ) return result.output except Exception as e: # Fallback: Reject on compliance failure return AgentDecision( approved=False, reasoning=f"Compliance check failed: {e}", confidence=0.0 )
Multi-Agent RAG for Trading Intelligence
For complex queries like “Should I buy AAPL?”, a single agent isn’t enough. Multi-Agent RAG coordinates specialized retrieval agents to gather comprehensive information:
flowchart TD
Q[User Query] --> C[Retrieval Coordinator]
C --> |Analyze| D[Determine Required Agents]
D --> M[Market Data Agent]
D --> F[Fundamental Agent]
D --> N[News Sentiment Agent]
D --> R[Risk Agent]
M --> S[Synthesis Agent]
F --> S
N --> S
R --> S
S --> G[Gap Analysis]
G --> |Complete?| OUT{Output}
OUT -->|Yes| ANS[Final Answer]
OUT -->|No| C
classDef blueClass fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff
classDef orangeClass fill:#F39C12,stroke:#333,stroke-width:2px,color:#fff
classDef greenClass fill:#27AE60,stroke:#333,stroke-width:2px,color:#fff
class C blueClass
class S orangeClass
class G greenClass
Key Roles in Multi-Agent RAG
Role
Responsibility
Retrieval Coordinator
Analyzes query, delegates to appropriate agents
Specialized Agents
Each focused on a specific data source (market, fundamentals, news, risk)
@self.agent.tool defanalyze_query_intent(ctx: RunContext, query: str) -> str: """Analyze what the query is asking for""" keywords = query.lower() intents = []
ifany(word in keywords for word in ['price', 'volume', 'technical']): intents.append("MARKET_DATA") ifany(word in keywords for word in ['earnings', 'revenue', 'financial']): intents.append("FUNDAMENTAL_DATA") ifany(word in keywords for word in ['news', 'sentiment', 'analyst']): intents.append("NEWS_SENTIMENT") ifany(word in keywords for word in ['risk', 'volatility', 'beta']): intents.append("RISK_METRICS")
returnf"Query intents: {', '.join(intents) if intents else'GENERAL'}"
defcoordinate(self, query: str, symbol: str) -> CoordinatorDecision: """Coordinate retrieval based on query""" context = RAGContext(query=query, symbol=symbol) result = self.agent.run_sync( f"Analyze this query and determine which retrieval agents are needed: {query}", deps=context ) return result.output
Gap Analysis and Re-Querying
The gap analyzer ensures completeness before returning results:
Fallback mechanisms are critical - always handle agent failures to prevent system deadlock
Multi-Agent RAG coordinates specialized retrieval agents (market, fundamental, news, risk) with synthesis and gap analysis for comprehensive trading intelligence
This is the thirteenth post in my Applied Agentic AI for Finance series. Next: Applied Agentic AI in Finance: A Complete Guide where we’ll bring together everything we’ve learned into a comprehensive overview of production patterns.
Comments