Agents with opinions: a weekend multi-agent build
In short: I built a small stock analyzer where five agents, each with a fixed investing style, look at the same company and give their own reading. The per-agent outputs are not the interesting part.…
- published
- read time
- 5 min
- words
- 961
- lang
- en
- filed under
- Engineering
In short: I built a small stock analyzer where five agents, each with a fixed investing style, look at the same company and give their own reading. The per-agent outputs are not the interesting part. The interesting part is what it takes to make several agents disagree usefully.
Most agent demos I see are one assistant with a long list of tools. I wanted to try the opposite: several narrow agents with strong, different opinions, and a way to put their answers side by side. Markets are a good playground for that because there are well known investing philosophies that flatly contradict each other, and the data is free.
What I built
The shape is boring on purpose. A FastAPI backend, a Next.js frontend, market data from yfinance, and either OpenAI or Anthropic models behind the agents. Every analysis is saved to SQLite or PostgreSQL, so I can look back at what the agents said about a ticker last week.
You type a ticker, tick the agents you want, and press analyze. Each agent comes back with its own signal and a confidence score. Three endpoints do the work: POST /api/analyze, GET /api/agents and GET /api/history/{ticker}.
The cast
| Agent | Philosophy | Looks at | Tends to worry about |
|---|---|---|---|
| Value style | Intrinsic value against market price, with a margin of safety | Earnings consistency, durable advantages, quantitative fundamentals | Paying too much |
| Growth style | Growth from fundamentals and trends | Company fundamentals, market opportunities | Missing the run |
| Technical style | Price action and indicators | RSI, MACD, moving averages, Bollinger Bands, volume | Momentum turning |
| Sentiment style | What the crowd thinks | News and social sentiment, analyst ratings, volume surges | Mood shifts |
| Risk manager | Protect the portfolio | Volatility, VaR, Sharpe ratio, downside deviation | Position size |
Four of the agents are investing styles that have been written about for decades: value, growth, technical and sentiment. The fifth is a role rather than a style, and its only job is risk. The mix matters. Styles give you disagreement. The risk manager gives you a check on it.
Why disagreement is the feature
If you blend five agents into one score you get a number with no story. If you show them side by side you get information: when the value and growth agents agree with the risk manager, that says something about the agents' shared assumptions, not about the stock. When only the technical and sentiment agents are excited, that's another pattern worth reading. The spread tells you more than any single answer.
That's the main design choice in the build. Results come back per agent, each with its own confidence, and the interface puts them next to each other. I'd rather read the spread than a blended score.
Design lessons
Narrow agents, written down principles
Each agent has one philosophy and is told what to look at. A prompt that says "be a value investor" gives you a caricature. A prompt that lists what that philosophy actually checks, intrinsic value against price, consistency of earnings, a moat, gives you something you can argue with. A label is not enough. The principles do the work, not the name.
Numbers from code, judgement from the model
My rule for anything numeric: indicators like RSI, volatility or a Sharpe ratio get calculated in Python, and the model reads them. Asking a language model to compute a moving average is asking for a confident wrong number.
One base class
Every agent inherits from BaseAgent and implements analyze(). Adding a sixth is a new class, one line in the analysis service, and one entry in the frontend list. A simplified sketch of the shape, not the repo's exact code:
class BaseAgent:
name = "base"
def analyze(self, ticker: str, data: dict) -> dict:
raise NotImplementedError
class RiskManager(BaseAgent):
name = "risk"
def analyze(self, ticker, data):
metrics = risk_metrics(data["prices"]) # plain Python, no LLM
text = self.llm.explain(self.principles, metrics)
return {"agent": self.name, "signal": text.signal,
"confidence": text.confidence, "reasoning": text.reasoning}
Work without keys
If no API keys are set, the agents return mock responses. That sounds like a small thing. It meant I could build the whole frontend, the database and the API without spending a cent on tokens, and anyone cloning the project can see it run before deciding to plug in a key.
What I'd change next
Right now the agents don't actually talk to each other. They each read the same data and answer on their own. The disagreement is real, but it's parallel, not a conversation. The next step is a second round where the risk manager reads the other four readings and has to respond to them, or where each agent sees the others and can revise once. That's where multi-agent setups get interesting and where they get expensive, so I want to measure whether a second round changes anything before keeping it.
The saved history matters here too. If an agent flips its signal on the same company from one day to the next with no new data, that's a prompt problem, not a market signal.
If you want to try this pattern, start with two agents that are designed to disagree and one that only judges risk. Run them on the same input, print the answers side by side, and see whether the disagreement teaches you anything. If it doesn't, adding four more agents won't help. And whatever comes out is a demonstration of multi-agent design, not something to trade on.
related