# How to Add a Custom Analyst Agent to ai-hedge-fund

> Learn how to add a custom analyst agent to ai-hedge-fund by creating and registering a Python function. Integrate your custom agent seamlessly into the LangGraph workflow via the CLI.

- Repository: [Virat Singh/ai-hedge-fund](https://github.com/virattt/ai-hedge-fund)
- Tags: how-to-guide
- Published: 2026-03-09

---

**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`](https://github.com/virattt/ai-hedge-fund/blob/main/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`](https://github.com/virattt/ai-hedge-fund/blob/main/src/utils/analysts.py), which exports `ANALYST_CONFIG`—a dictionary mapping analyst keys to their metadata and function references. When [`src/main.py`](https://github.com/virattt/ai-hedge-fund/blob/main/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`](https://github.com/virattt/ai-hedge-fund/blob/main/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`](https://github.com/virattt/ai-hedge-fund/blob/main/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.

```python
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`](https://github.com/virattt/ai-hedge-fund/blob/main/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).

```python

# 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`.

```bash
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 `AgentState` and returning messages and data updates.
- **Register in one file**: Adding an entry to `ANALYST_CONFIG` in [`src/utils/analysts.py`](https://github.com/virattt/ai-hedge-fund/blob/main/src/utils/analysts.py) exposes the agent to the entire system.
- **No core changes needed**: The workflow builder in [`src/main.py`](https://github.com/virattt/ai-hedge-fund/blob/main/src/main.py) dynamically constructs the graph from the registry, eliminating the need to modify orchestration logic.
- **CLI integration is automatic**: The `ANALYST_ORDER` list derives directly from `ANALYST_CONFIG`, making new analysts instantly selectable via `--analysts` flags 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`](https://github.com/virattt/ai-hedge-fund/blob/main/src/main.py) calls `get_analyst_nodes()` from [`src/utils/analysts.py`](https://github.com/virattt/ai-hedge-fund/blob/main/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`](https://github.com/virattt/ai-hedge-fund/blob/main/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.