webapp — Full-Stack Web Application

Full-Stack Web Application

Visual workflow builder with FastAPI backend and React frontend. Build agent pipelines via drag-and-drop, run them with real-time SSE streaming, and manage saved flows and execution history.

🎯 Why This Module Exists

The CLI is powerful for developers but inaccessible to non-technical users and lacks persistence. The web application adds three critical capabilities: (1) Visual workflow building — drag-and-drop agent composition using React Flow, where each agent is a node and edges define execution order; (2) Persistent configuration — saved flows and execution history in SQLite via SQLAlchemy ORM, so users can iterate on strategies without re-entering parameters; and (3) Real-time feedback — Server-Sent Events (SSE) streaming shows agent-by-agent progress as the pipeline executes, with each agent's status, ticker, and analysis pushed to the browser as it completes. Without the web app, users would need to edit Python code to change agent configurations, pass complex CLI arguments for every run, and every execution would be ephemeral with no history.

💡 Real-world analogy

If the CLI is like programming a synthesizer by typing MIDI commands into a terminal, the web app is like GarageBand — a visual interface where you drag instrument tracks (agents) onto a timeline (workflow), connect them with wires (edges), press play, and watch each track light up in real time as it plays (SSE streaming). You can save your compositions (flows) and replay them later, and the mixing console (backtest dashboard) shows you how the ensemble performed.

142
Files
2
Sub-Modules
~18.3k
Lines of Code
Py + TS
Languages

React Flow SSE Streaming FastAPI + SQLite

Architecture

flowchart LR
  subgraph Frontend["Frontend (React + TypeScript)"]
    RF["React Flow\nCanvas"]
    Hooks["Custom Hooks\n(12 hooks)"]
    Services["API Services\nfetch + SSE"]
  end
  subgraph Backend["Backend (FastAPI + Python)"]
    Routes["Routes\n/hedge-fund, /backtest\n/flows, /settings"]
    Svc["Services\nGraphService\nBacktestService"]
    DB["SQLAlchemy ORM\nSQLite"]
  end
  subgraph Core["Core (src/)"]
    Agents["Agent Functions\n(analysts)"]
    Graph["StateGraph\nWorkflow"]
  end
  RF --> Hooks
  Hooks --> Services
  Services -->|"HTTP + SSE"| Routes
  Routes --> Svc
  Svc --> DB
  Svc --> Graph
  Graph --> Agents

Data Flow

sequenceDiagram
    participant User as Browser
    participant FE as React Frontend
    participant API as FastAPI Backend
    participant GS as GraphService
    participant Core as src/ Agents
    User->>FE: Drag agents, connect edges, click Run
    FE->>API: POST /hedge-fund/run (nodes, edges, tickers, model)
    API->>GS: create_graph(nodes, edges)
    GS->>GS: Build StateGraph from React Flow structure
    GS->>Core: graph.invoke(AgentState)
    loop For each agent
        Core-->>API: progress.update_status(agent, ticker, analysis)
        API-->>FE: SSE event (ProgressUpdate)
        FE-->>User: Agent card lights up with status
    end
    Core-->>GS: Final portfolio decision
    GS-->>API: Result dict
    API-->>FE: SSE event (CompleteEvent)
    FE-->>User: Display trading recommendations

🚶 Step-by-Step Narrated Walkthrough

Follow a single user running a hedge fund workflow through the web UI from start to finish:

  1. Canvas Setup — The user drags Warren Buffett and Ben Graham agent nodes onto the React Flow canvas, then connects both to a Portfolio Tracking & Performance Metrics" href="../backtesting_doc/portfolio/index.html">Portfolio Manager node with edges.
  2. Configuration — The user selects tickers (AAPL, GOOGL), date range, initial cash ($100k), and chooses GPT-4o as the model. These are captured by custom React hooks.
  3. Request Dispatch — Clicking "Run" triggers a POST to /hedge-fund/run with a HedgeFundRequest payload containing graph_nodes, graph_edges, tickers, and model config.
  4. API Key Hydration — The route handler checks if API keys were provided in the request. If not, it loads them from the database via ApiKeyService.
  5. Orchestration" href="../core_doc/graph/index.html">Graph Construction — create_graph() in services/graph.py maps React Flow node IDs to agent functions using ANALYST_CONFIG. It strips 6-character suffixes (extract_base_agent_key) to match unique canvas IDs to base agent names.
  6. Edge Wiring — The service iterates edges, detects direct analyst-to-portfolio-manager connections, and automatically interposes a Risk Manager node. Entry-point agents (no incoming edges) are connected to start_node.
  7. SSE Stream Opens — The route returns a StreamingResponse. A progress handler is registered that pushes ProgressUpdateEvent objects to an asyncio.Queue.
  8. Async Execution — run_graph_async() wraps the synchronous graph.invoke() in loop.run_in_executor() so it runs in a thread pool without blocking the event loop.
  9. Progress Streaming — As each agent completes, it calls progress.update_status(). The registered handler enqueues a ProgressUpdateEvent. The event generator dequeues it and yields an SSE-formatted string to the browser.
  10. Client Disconnect Detection — A wait_for_disconnect() coroutine monitors for http.disconnect messages, allowing cleanup if the user navigates away mid-run.
  11. Completion — After all agents finish, parse_hedge_fund_response() extracts the final JSON. A CompleteEvent is pushed to the SSE stream with trading recommendations.
  12. Frontend Update — The React app parses each SSE event, updates agent card states (pending, running, complete), and renders the final portfolio allocation table.

How webapp Connects to core

The web application is a thin orchestration layer on top of the core infrastructure. It does not duplicate any agent logic — instead, it imports AgentState, StateGraph, and agent functions directly from src/. The boundary is clean: the backend constructs a graph from React Flow data, invokes it through the same graph.invoke() path as the CLI, and wraps the output in SSE events.

flowchart TD
  subgraph WebApp["webapp (app/)"]
    Routes["FastAPI Routes"]
    GraphSvc["GraphService\ncreate_graph()"]
    BacktestSvc["BacktestService"]
  end
  subgraph Core["core (src/)"]
    AgentState["AgentState"]
    Agents["Agent Functions"]
    Start["start() entry"]
    AnalystConfig["ANALYST_CONFIG"]
  end
  GraphSvc -->|"imports"| AgentState
  GraphSvc -->|"imports"| Agents
  GraphSvc -->|"imports"| Start
  GraphSvc -->|"imports"| AnalystConfig
  Routes -->|"calls"| GraphSvc
  BacktestSvc -->|"calls"| GraphSvc
Key insight: webapp owns the HTTP layer, SSE streaming, persistence, and visual workflow construction. core owns the agent logic, state management, and LLM/data infrastructure. The boundary is the StateGraph — webapp builds it from UI data, core executes it.

🏗️ Design Decisions

DecisionChoice MadeAlternatives ConsideredWhy This Approach
Real-time updates Server-Sent Events (SSE) WebSockets, polling, long-polling SSE is unidirectional (server to client), matching the progress-update pattern perfectly. Simpler than WebSocket (no handshake, auto-reconnect). Works with standard HTTP infrastructure without special proxy config.
Graph construction from UI Map React Flow nodes/edges to StateGraph at request time Pre-compiled graph templates, serialized graph config Dynamic construction allows arbitrary agent topologies. The extract_base_agent_key function strips canvas-unique suffixes, enabling multiple instances of the same agent type in one workflow.
Risk manager interposition Auto-insert risk manager between analysts and portfolio manager Require explicit risk manager nodes, skip risk management Enforces the risk management step without burdening the UI. Users connect analysts to portfolio managers directly; the backend transparently routes through risk assessment first.
Async execution run_in_executor wrapping synchronous graph.invoke() Native async agents, Celery task queue The core agents are synchronous (LangChain/LangGraph). Running in a thread pool avoids blocking the FastAPI event loop while keeping the existing agent code unchanged. No external task queue dependency.
Database SQLite via SQLAlchemy ORM PostgreSQL, MongoDB, file-based JSON Zero deployment dependencies. SQLAlchemy provides migration support and type safety. SQLite is sufficient for single-user or small-team usage patterns typical of a trading research tool.

🔬 Algorithm Spotlight

React Flow to StateGraph Translation

The Problem: The React Flow canvas produces a flat list of nodes (with unique IDs like warren_buffett_a1b2c3) and edges (source/target pairs). This must be translated into a LangGraph StateGraph with proper entry points, agent wiring, and mandatory risk management steps.

The Approach: The create_graph() function performs a multi-pass translation: (1) Extract base agent keys by stripping 6-char suffixes. (2) Register agent nodes, tracking portfolio managers separately. (3) For each portfolio manager, create a paired risk manager with a matching suffix. (4) Scan edges: direct analyst-to-portfolio-manager connections are rerouted through the risk manager. (5) Nodes with no incoming agent edges become entry points connected to start_node. (6) Portfolio managers connect to END.

Why This Algorithm: A single-pass approach would require lookahead to handle the risk manager interposition. The multi-pass design keeps each concern isolated: node registration, edge wiring, and entry/exit point detection are independent passes over the data.

flowchart TD
    A["React Flow nodes + edges"] --> B["Pass 1: Extract base agent keys\nstrip 6-char suffixes"]
    B --> C["Pass 2: Register agent nodes\nseparate portfolio managers"]
    C --> D["Pass 3: Create risk manager\nfor each portfolio manager"]
    D --> E["Pass 4: Wire edges\ninterpose risk managers"]
    E --> F["Pass 5: Connect entry points\nto start_node"]
    F --> G["Pass 6: Connect portfolio\nmanagers to END"]
    G --> H["Compiled StateGraph"]
Trace through an example

Given: Canvas has nodes [warren_buffett_a1b2c3, ben_graham_d4e5f6, portfolio_manager_g7h8i9] and edges [buffett->PM, graham->PM].

Pass 1: Extract base keys: warren_buffett, ben_graham, portfolio_manager.

Pass 2: Register warren_buffett_a1b2c3 and ben_graham_d4e5f6 as agent nodes. portfolio_manager_g7h8i9 goes into the portfolio manager set.

Pass 3: Create risk_management_agent_g7h8i9 paired with the portfolio manager.

Pass 4: Both edges target a portfolio manager, so they are rerouted: buffett → risk_manager, graham → risk_manager, risk_manager → portfolio_manager.

Pass 5: Neither analyst has incoming edges, so both connect to start_node.

Result: start_node → [buffett, graham] → risk_manager → portfolio_manager → END

⚠️ What Could Go Wrong

Failure ModeTriggerDetectionRecovery / Mitigation
Client disconnects mid-run User navigates away or closes the browser tab during execution wait_for_disconnect() coroutine receives http.disconnect message The disconnect task triggers cleanup. However, the graph execution thread continues to completion in the thread pool — results are discarded but resources are eventually freed.
Unknown agent node on canvas React Flow node ID does not match any entry in ANALYST_CONFIG extract_base_agent_key returns an unrecognized key; the if base_agent_key not in ANALYST_CONFIG check triggers The node is silently skipped during graph construction. The workflow executes with only recognized agents.
SSE stream timeout Agent execution takes too long (e.g., rate-limited API calls with 120s retries) Browser or reverse proxy closes the SSE connection after idle timeout The progress queue ensures events are buffered. If the connection drops, the frontend can show the last received state. The backend continues execution regardless.
Database migration mismatch Schema changes between versions Base.metadata.create_all(bind=engine) at startup SQLAlchemy's create_all is additive (creates missing tables) but does not alter existing tables. Breaking schema changes require manual migration.
CORS rejection Frontend served from a URL not in the allowed origins list Browser blocks the request; no server-side error Allowed origins are hardcoded to localhost:5173 and 127.0.0.1:5173. Production deployments must update the CORS middleware configuration.

⚡ Performance Profile

Hot Paths

The most performance-sensitive code paths in the web application:

Resource Usage

Sub-Modules

backend/

FastAPI REST API with SSE streaming, SQLAlchemy ORM, and repository pattern.

  • Routes: /hedge-fund, /backtest, /flows, /settings
  • Services: GraphService, BacktestService, ApiKeyService
  • SQLite database with SQLAlchemy models

frontend/

React + TypeScript visual workflow builder with React Flow and 12 custom hooks.

  • Drag-and-drop agent canvas with React Flow
  • SSE-powered real-time progress display
  • Backtest dashboard and flow management

Directory Map

Source tree for webapp
app/
├── backend/                       ← FastAPI server
│   ├── main.py                    ← App factory, CORS, startup events
│   ├── routes/                    ← API route handlers
│   │   ├── hedge_fund.py          ← /hedge-fund/run (SSE streaming)
│   │   ├── backtest.py            ← /backtest endpoint
│   │   ├── flows.py               ← CRUD for saved workflows
│   │   └── settings.py            ← API key and Ollama management
│   ├── services/                  ← Business logic layer
│   │   ├── graph.py               ← create_graph(), run_graph_async()
│   │   ├── backtest_service.py    ← Backtesting orchestration
│   │   ├── portfolio.py           ← Portfolio construction
│   │   └── ollama_service.py      ← Local model management
│   ├── database/                  ← Persistence layer
│   │   ├── connection.py          ← SQLite engine setup
│   │   └── models.py              ← SQLAlchemy ORM models
│   └── models/                    ← Request/response schemas
│       ├── schemas.py             ← Pydantic request models
│       └── events.py              ← SSE event types
└── frontend/                      ← React application
    ├── src/
    │   ├── components/            ← UI components
    │   ├── hooks/                 ← 12 custom React hooks
    │   ├── services/              ← API client + SSE handler
    │   └── App.tsx                ← Root component
    ├── package.json
    └── vite.config.ts             ← Build configuration