How to Add a Custom Analyst Agent to ai-hedge-fund
Adding a custom analyst to ai-hedge-fund requires creating a function that accepts an AgentState and returns a dictionary with messages, then registering it in ANALYST_CONFIG inside src/utils/analysts.py. The LangGraph workflow dynamically loads analysts from this central registry, making your new agent immediately selectable via CLI flags without modifying the core orchestration code.
The ai-hedge-fund project implements a modular graph architecture where each analyst operates as a discrete node in the decision workflow. By leveraging the centralized analyst configuration system, you can inject bespoke investment logic—whether proprietary sentiment analysis, alternative data signals, or custom technical indicators—into the pipeline by following a strict interface contract.
Understanding the Analyst Node Architecture
The hedge fund engine constructs its workflow as a LangGraph state machine. Each analyst is a node that receives the shared AgentState, performs computations, and returns updates that flow to the risk manager and portfolio manager nodes.
The system discovers available analysts through src/utils/analysts.py, which exports ANALYST_CONFIG—a dictionary mapping analyst keys to their metadata and function references. When src/main.py initializes the graph, it calls get_analyst_nodes() to obtain a mapping of analyst keys to (node_name, agent_func) tuples, then wires them into the workflow via create_workflow().
Because the CLI in src/cli/input.py derives its selectable options from ANALYST_ORDER (automatically generated from ANALYST_CONFIG), new analysts appear instantly in the interactive interface without additional registration steps.
Step 1: Implement the Agent Function
Create a new Python file in src/agents/ (for example, src/agents/custom_alpha.py). Your function must accept two parameters: state (an AgentState dictionary) and agent_id (an optional string identifier). It must return a dictionary containing a messages key with a list containing a HumanMessage, and a data key with the updated state.
The function should update state["data"]["analyst_signals"][agent_id] with its analysis results and utilize progress.update_status() to report execution status.
from langchain_core.messages import HumanMessage
from src.graph.state import AgentState, show_agent_reasoning
from src.utils.progress import progress
import json
class CustomAlphaSignal:
"""Simple signal container – replace with a Pydantic model if desired."""
def __init__(self, signal: str, confidence: int, reasoning: str):
self.signal = signal
self.confidence = confidence
self.reasoning = reasoning
def custom_alpha_agent(state: AgentState, agent_id: str = "custom_alpha_agent"):
"""Example analyst that returns a neutral signal for all tickers."""
tickers = state["data"]["tickers"]
analysis = {}
for ticker in tickers:
progress.update_status(agent_id, ticker, "Generating static signal")
analysis[ticker] = {
"signal": "neutral",
"confidence": 50,
"reasoning": "No specific logic implemented yet",
}
# Prepare LLM-compatible message payload
message = HumanMessage(content=json.dumps(analysis), name=agent_id)
# Display reasoning if requested
if state["metadata"]["show_reasoning"]:
show_agent_reasoning(analysis, "Custom Alpha Analyst")
# Persist results to shared state
state["data"]["analyst_signals"][agent_id] = analysis
progress.update_status(agent_id, None, "Done")
return {"messages": [message], "data": state["data"]}
Step 2: Register in ANALYST_CONFIG
Open src/utils/analysts.py and import your function at the top of the file. Then append a new entry to the ANALYST_CONFIG dictionary with the following required fields: display_name, description, investing_style, agent_func, type (set to "analyst"), and order (an integer determining execution sequence).
# Add import at the top of src/utils/analysts.py
from src.agents.custom_alpha import custom_alpha_agent
# Append to ANALYST_CONFIG (order=17 places it after built-ins)
ANALYST_CONFIG["custom_alpha"] = {
"display_name": "Custom Alpha",
"description": "User-defined placeholder analyst",
"investing_style": "Neutral baseline – can be extended with any strategy",
"agent_func": custom_alpha_agent,
"type": "analyst",
"order": 17,
}
The agent_func must reference the function object itself (not a string). The order field determines where the analyst appears in the CLI list and its execution priority relative to other analysts.
Step 3: Execute via Command Line
Your custom analyst is now selectable using the --analysts flag. The workflow will invoke your function exactly as it does built-in analysts like warren_buffett or ben_graham.
python -m src.main \
--tickers AAPL,MSFT \
--analysts custom_alpha \
--model gpt-4o \
--show-reasoning
The CLI validates the analyst key against ANALYST_CONFIG, constructs the graph with get_analyst_nodes(), and routes the state through your custom logic before passing signals to the risk management layer.
Key Implementation Details
State Contract: Every analyst receives the same AgentState object containing data (shared financial data and signals) and metadata (configuration flags). Modifications to state["data"] persist across the graph execution.
Message Format: The returned messages list must contain HumanMessage objects with JSON-serialized content representing the analysis. This format ensures compatibility with the portfolio manager's parsing logic.
Progress Reporting: Always wrap ticker-level processing with progress.update_status(agent_id, ticker, status_message) to provide real-time feedback in the CLI interface.
Reasoning Display: When state["metadata"]["show_reasoning"] is true, call show_agent_reasoning(analysis, agent_name) to print structured output for debugging.
Summary
- Analysts are nodes: Each custom analyst is a LangGraph node receiving
AgentStateand returning messages and data updates. - Register in one file: Adding an entry to
ANALYST_CONFIGinsrc/utils/analysts.pyexposes the agent to the entire system. - No core changes needed: The workflow builder in
src/main.pydynamically constructs the graph from the registry, eliminating the need to modify orchestration logic. - CLI integration is automatic: The
ANALYST_ORDERlist derives directly fromANALYST_CONFIG, making new analysts instantly selectable via--analystsflags or interactive checkboxes.
Frequently Asked Questions
What parameters must a custom analyst agent function accept?
The function signature must accept state: AgentState as the first positional argument and agent_id: str as the second (typically with a default value matching the config key). It must return a dictionary with exactly two keys: messages (a list of HumanMessage objects) and data (the updated state dictionary).
Do I need to modify src/main.py to add a new analyst?
No. The create_workflow() function in src/main.py calls get_analyst_nodes() from src/utils/analysts.py, which dynamically constructs the node mapping from ANALYST_CONFIG. As long as your analyst is registered in that dictionary, it will be automatically included in the graph.
How does the CLI discover my custom analyst?
The CLI in src/cli/input.py imports ANALYST_ORDER, which is generated from the keys of ANALYST_CONFIG. When you add a new entry to the configuration dictionary with a unique key and proper display_name, it immediately appears in the interactive analyst selection menu and becomes valid input for the --analysts argument.
Can I use Pydantic models instead of raw dictionaries for the signal output?
Yes. While the example uses a simple dictionary structure for the analysis variable, you can define Pydantic models for type validation. However, you must still serialize the final output to JSON when creating the HumanMessage content, as the portfolio manager expects string payloads that it can parse into structured decisions.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →