Inspiration

Most AI-powered investment tools ask a single model to "analyze" a stock and produce a balanced take. The problem is that LLMs are trained to hedge. Ask one to evaluate NVDA, and it will tell you the bull case and the bear case in the same breath.

The insight that drove AlphaWalker came from debate theory and adversarial ML research. Models are far better at arguing a position than at balancing one. Neither would surface real catalysts and risks if asked to "be objective."

We also wanted to build something that felt native to Jac, where the graph topology is the architecture. The two-coalition structure maps directly onto Jac's Object Spatial Programming model. Two walkers traversing an asset node from opposite sides, depositing findings, with a judge walker arbitrating after both have landed.

What It Does

AlphaWalker takes a list of stock tickers and runs each one through a structured adversarial debate between two AI agent coalitions.

The Bull Coalition builds the strongest possible buy case across three specialist agents: a news agent hunting positive catalysts (earnings beats, analyst upgrades, product launches), a quant agent reading price action and fundamentals for bullish signals, and a macro agent identifying favorable sector tailwinds and capital flow trends.

The Bear Coalition mirrors this structure exactly, but is prompted with the opposite directive. It finds every reason to avoid or sell. It surfaces regulatory risk, deteriorating margins, valuation excess, and macro headwinds.

The Judge Agent does not conduct its own research. It reads the structured arguments deposited by both coalitions, evaluates evidence quality and confidence scores, and renders one of six verdicts: conviction buy, buy with conditions, hold, avoid with conditions, conviction avoid, or split decision. When both sides return low confidence, the judge returns INSUFFICIENT DATA — DO NOT TRADE, which is often the most valuable output a system can produce.

The full output is an investment committee memo per ticker: bull confidence, bear confidence, a confidence-weighted judge ruling, a rationale paragraph, and any conditional watch flags.

A React dashboard surfaces all of this in three tabs: a compact verdict card view for a full portfolio, a detailed breakdown with confidence bar charts and a radial judge gauge, and a history tab pulling past runs from a persistent SQLite database.

How We Built It

The core of AlphaWalker is written in Jac.

The graph topology maps the domain directly:

  • Asset nodes hold ticker data and serve as the arena where both coalitions deposit their findings
  • BullNode and BearNode attach to each asset on opposing edges via HasCoalition edges
  • VerdictNode receives the judge's output via HasVerdict edges
  • Everything hangs off a PortfolioNode at the root

The two coalition walkers — BullCoalitionWalker and BearCoalitionWalker — traverse simultaneously. Each internally orchestrates three sub-agents (news, quant, macro), each of which uses Jac's by llm() construct with a directed argumentative system prompt to generate its findings and a confidence score. The coalition walker then synthesizes the sub-agent outputs into a single structured argument before depositing it onto the node.

JudgeWalker triggers only after both coalitions have written their arguments. It reads both sides using Jac's graph query syntax ([asset ->:HasCoalition:side=="bull":->]) and runs the final synthesis through another by llm() call with a neutral arbitration prompt.

The FastAPI backend (api/main.py) exposes POST /v1/analyze-tickers. Because of a Jac codegen limitation with subprocess.run kwargs, we bridged Python and Jac using a dedicated jac_bridge.py (Python) that spawns _jac_worker_main.py as a subprocess — this pattern let us keep the Jac graph pure while still integrating cleanly with a Python web server. Results are serialized to JSON in alphawalker_pipeline.jac and parsed by FastAPI before being stored in SQLite and returned to the frontend.

The React frontend was built with Vite and runs on port 3000. It hits the FastAPI backend at port 8000, displays results in real time, and reads run history from the /v1/history endpoint. The confidence bar chart and radial judge gauge were built with Recharts.

The entire stack runs with a single ./start.sh — it handles both backend and frontend boot.

Challenges We Ran Into

Prompt architecture for adversarial agents is harder than it looks. The first version of the coalitions produced polite, hedged arguments that defeated the whole point. We had to iterate heavily on the by llm() system prompts — explicitly forbidding balance, requiring the agent to steel-man its own side, and penalizing hedging language — before the coalitions started producing genuinely opposed outputs.

Synchronizing the two coalition walkers took careful design. Jac's walker model is naturally sequential within a single traversal; getting the bull and bear walkers to run in parallel and ensuring the JudgeWalker only triggered after both had deposited their findings required explicit coordination logic in the pipeline rather than relying on the runtime to handle it automatically.

The Python-Jac boundary introduced friction throughout the stack. The FastAPI layer is Python; the core logic is Java; the graph outputs needed to be serialized to JSON in a way Python could parse. We ended up writing a manual json_str() escape function in Jac because Jac's standard library JSON support had rough edges, and built the entire output row as a concatenated JSON string before handing it back to Python.

LLM latency at the portfolio level. Running six by llm() calls per ticker (three bull sub-agents + three bear sub-agents + judge) means a five-ticker portfolio involves 35+ LLM calls. We documented this in DEPLOYMENT.md and added the ALPHAWALKER_JAC_TIMEOUT env var with a 900-second default, but managing the UX around this latency required thoughtful handling.

Accomplishments That We're Proud Of

We actually built it in Jac. The graph, the walkers, the node topology, and the by llm() agent calls are all native Jac. Getting a multi-walker adversarial pipeline running end-to-end in a hackathon timeframe was a real challenge.

The judge's humility. The INSUFFICIENT DATA — DO NOT TRADE verdict isn't a fallback or an error state — it's a first-class output. Building a system that knows when not to act and surfaces that explicitly was a deliberate design decision we're proud of.

The temporal graph design. Rather than overwriting previous runs, each walker pass appends a timestamped node, preserving the full history of how signals evolved. This enables a qualitatively different class of reasoning.

The full-stack integration. CLI → FastAPI → React, with a Python/Jac subprocess bridge that actually works reliably, a SQLite history store, Ollama support for local LLM runs, and a one-command startup script. It's a complete, deployable system.

What We Learned

In a traditional agent framework, you're writing orchestration logic. In Jac, you define the topology and the walkers, and the orchestration falls out of the graph structure. Once we internalized that, the architecture became significantly cleaner.

Adversarial prompting is a real technique, not a gimmick. The difference in output quality between a balanced single-agent prompt and two opposed agents with restricted directives was substantial and consistent. The coalitions found risks and catalysts that a neutral prompt reliably buried.

The Python-Jac bridge pattern. Using a dedicated Python subprocess entrypoint to invoke Jac pipelines, with JSON as the IPC format, is a clean architectural boundary that we'd use again in any Python-Jac hybrid project.

What's Next for AlphaWalker

We will add a new feature: Deep Analysis. It activates a second-pass pipeline that runs after the standard bull/bear/judge cycle and layers six new walker behaviors on top of the existing graph.

1. Walkers that spawn walkers mid-traversal. Right now, the bull coordinator runs its three sub-agents in a fixed sequence. In Deep Analysis mode, that changes. The news walker visits a BullNode, scores sentiment, and if it detects something anomalous — an earnings surprise, a CEO resignation, an FDA approval — it spawns a new deep-dive child walker on the spot, targeting that specific signal. The child walker pulls more sources, reasons harder, and deposits enriched findings back onto the node before the parent continues. This is recursive agent spawning: a fundamentally different execution model that traditional frameworks can't express naturally.

2. The Judge as a multi-round debater

The current JudgeWalker reads both sides once and rules. In Deep Analysis mode, it becomes adversarial internally. Round 1: Judge reads the Bull argument and generates a rebuttal. Round 2: Judge reads the Bear argument and generates a rebuttal. Round 3: Judge reads both rebuttals and renders a final ruling. The judge is stress-testing each argument before committing. The VerdictNode stores not just the final verdict but the full three-round debate transcript.

3. Cross-asset contagion walkers.

Deep Analysis adds a ContagionWalker that traverses between Asset nodes via sector edges. If NVDA receives a strong bear signal, the contagion walker automatically visits AMD, INTC, and QCOM and deposits a contagion_warning on each before their coalition walkers even run. The coalitions then read that warning as part of their analysis context.

4. Agents that learn from their own history.

Since the temporal graph already preserves TemporalSnapshot nodes from every past run, Deep Analysis adds a HistorianWalker that runs before the coalitions start. It traverses all past snapshots for each asset and generates a prior.

5. The query walker becomes a Socratic agent.

In standard mode, the QueryWalker retrieves and summarizes graph data. In Deep Analysis mode, it becomes argumentative. When you ask "why should I sell TSLA?" it runs three chained by llm() calls in a single traversal, generating a counter-argument to your question, then responding to that counter-argument, then giving you its actual answer.

Built With

Share this project:

Updates