-
-
Architecture
-
homepage with "Open Terminal" CTA visible
-
NVDA event selected, supply-chain arcs drawn on globe, cascade panel filled on right
-
Press C, full 2D cascade graph centered
-
Press V, 3D cascade with orbit rings visible, ideally mid-rotation
-
Pin 2 events, side-by-side graph view
-
Society panel open showing all 4 agents (Critic, Predictor, Memory, ELI5) populated
-
"Run worker agents" button circled / arrow-annotated in red
-
-
MongoDB Collections
-
Search Indices
💡 Inspiration
Markets don't move in straight lines. They move in cascades. Picture a Tuesday across three time zones:
05:42 GMT, Hsinchu. A 50,000-volt substation feeding TSMC's Fab 18 trips. Six hours of cold tools.
18:11 CET, Wolfsburg, three weeks later. Volkswagen pauses the ID.4 line. A press release blames "logistics."
23:08 GMT, Bab-el-Mandeb. A drone clips a Suezmax. Forty-eight hours later, a snack maker in Ohio quietly raises full-year guidance — palm-oil futures spiked and their hedges paid out.
Three apparently unrelated tape-bombs, one causal graph. That graph lives, today, inside the heads of sell-side analysts, in locked Bloomberg chats, and in PDFs nobody opens until Monday. By the time the second-order names move on the screen, the alpha is somebody else's.
I built CascadeTerminal because the instrument that draws this graph in real time doesn't exist outside an institutional desk, and a retail trader (or a curious engineer, or a generalist PM) deserves one.
🔭 What CascadeTerminal is
A terminal — the kind of artefact you leave open on a second monitor — that watches the world in real time and, the moment something breaks, draws you the cascade. Root event. Suppliers. Customers. Peers. The transmission mechanism. A historical analog. The why of every edge, in one sentence per edge.
Open cascade-terminal.vercel.app. A 3D globe is pulsing with everything that hit the wire in the last hour — SEC 8-Ks, breaking news, M≥5 earthquakes, severe-weather alerts, diverted flights, stalled tankers. Click any pulse. In under two seconds the right rail draws the 3-hop cascade tree:
root → suppliers → customers → peers
Each node carries a directional confidence score and a sentence of reasoning written by a Gemini 3 specialist.
Type "earnings misses that crashed semis last quarter" into the search bar; the answer is a list of past cascades ranked by narrative similarity, not ten blue links. Drag a chart PNG or a 10-K PDF onto the same bar and multimodal embeddings find the events that look like your image.
The hardest single move is tickerless events — a typhoon doesn't ship with $NVDA attached. Those route through a Gemini 3 Pro Coordinate Mapping System: one structured-output call infers affected regions, sector exposure, transmission mechanism, and a historical analog, then fans validated arcs from event lat/lon to the listed-equity HQs it expects to repaint next.
One MongoDB Atlas cluster does the data work. One Gemini 3 family does the reasoning. Solo build.
🪟 The surface
Ten visible features, each wired to a real piece of MongoDB, Voyage, or Google infrastructure underneath.
| Surface | What it does | What's underneath |
|---|---|---|
| 🌐 3D live globe | Geo-located events from the last hour pulse in real time; arcs overlay tickerless cascades. | react-globe.gl + Three.js, fed by SSE off a MongoDB change stream. |
| 🌲 3-hop cascade tree | Click an event; walk supplier / customer / peer / sector edges out three hops, ranked by narrative strength. | A single $graphLookup over 1,149 hand-seeded edges + Voyage rerank-2.5. |
| 🗺️ Coordinate Mapping System | Tickerless events (typhoons, coups, tariffs) become structured regions + sectors + transmission + analog. | Gemini 3 Pro JSON-mode + Pydantic + server-side lat ∈ [-90, 90], lon ∈ [-180, 180] validation. |
| 🧠 Agent Society | Five specialist agents collaborate on every cascade: Synthesizer, Critic, Predictor, Memory, Researcher. | Google ADK + Gemini 3 Pro orchestrator + Gemini 3 Flash specialists + MongoDB MCP server. |
| 🔍 Hybrid semantic search | "earnings misses that crashed semis" returns ranked cascades, not links. | $facet( $vectorSearch ⊕ Atlas $search ) → RRF → Voyage rerank-2.5. |
| 🖼️ Multimodal search | Drop a chart PNG or 10-K PDF; get the events that match. | voyage-multimodal-3.5 against the same vector index. |
| ⏪ Time-machine | Replay any day in the last 14, frame by frame. | TTL-bounded events + native time-series prices. |
| ✨ Counterfactual overlay | "What if this event had not happened?" — diff view over cascade nodes. | Memory agent + cached cascades + UI store flip. |
| 🪞 Compare mode | Pin two events; render two cascade graphs side by side. | Zustand compareIds, dual $graphLookup calls. |
| 🩺 Performance Advisor panel | The agent surfaces Atlas's own index suggestions in the UI. | MongoDB MCP server performance-advisor tool. |
Behind the surface sit two halves — a feeder pipeline and a reasoning brain, both pointed at the same Atlas cluster.
🛰️ Ingestion — eight workers, one schema
The ingestion layer isn't a script. It's eight independent worker agents, each a small Cloud Run Job that owns one source and one verb-chain: fetch → normalise → embed → insert. Any one can fail — rate limit, vendor outage, malformed payload — and the other seven keep filling the brain.
| Worker | Source | What it does each run |
|---|---|---|
| 📜 sec-edgar | SEC EDGAR | Pulls fresh 8-K / 10-Q / 10-K filings, extracts item-numbered sections, tags tickers from the filer index, normalises to the common Event schema. Sends User-Agent: Cascade research/contact@example.com — without it, EDGAR throttles to zero. |
| 📰 marketaux | Marketaux | Financial news + sentiment + hero image. The hero image is routed to voyage-multimodal-3.5 so charts in articles become part of the searchable index. |
| 💬 alpha-vantage | Alpha Vantage | A second-opinion sentiment signal so the cascade rerank never depends on a single vendor's mood. |
| 🌐 gdelt | GDELT 2.0 | Geopolitics firehose — tariffs, sanctions, coups, infrastructure incidents. Mostly tickerless, flagged for the Coordinate Mapping System downstream. |
| 🌋 usgs | USGS | Earthquakes ≥ M5.0 with epicentre lat/lon. |
| 🌪️ noaa | NOAA | Severe weather (hurricanes, ice storms) tagged with the affected region. |
| ✈️ opensky | OpenSky | Diverted, grounded, or anomalously low-altitude commercial flights near critical infrastructure. |
| 📈 yfinance | yfinance | OHLCV bars into the native time-series collection — the only worker that writes to prices rather than events. |
Three properties they share, learned by getting bitten:
- Stateless — no per-source database, no local cache. The cluster is the only memory.
- Idempotent on event hash — each worker computes a stable
external_idfromsource + native_id + published_atand upserts on it, so the same EDGAR filing seen on three consecutive scheduler invocations lands exactly once. - Isolated — a 429 from Marketaux does not page out SEC EDGAR. Each worker is its own container with its own retry budget.
📥 How a payload lands in MongoDB
Eight workers, eight vendors, one canonical document shape:
{
"_id": ObjectId(...),
"external_id": "edgar:0001045810-25-000123", # dedup key, unique index
"source": "sec-edgar",
"kind": "filing" | "news" | "quake" | "weather" | "aviation" | "vessel" | "social",
"tickers": ["NVDA"], # may be empty → Geo-Cascade path
"sector": "Semiconductors",
"headline": "NVDA · 8-K · Item 2.02 — Q3 results",
"text": "...full normalised body...", # Auto-Embedded server-side
"entities": ["TSMC", "Hsinchu", "HBM3"], # NER pass before insert
"geo": {"type": "Point", "coordinates": [120.99, 24.77]},
"impact": "high", # low | medium | high — drives change stream
"published_at": ISODate("2026-05-31T13:42:11Z"),
"fetched_at": ISODate("2026-05-31T13:42:47Z"),
"ttl_at": ISODate("2026-06-14T13:42:11Z") # +14d, TTL drops it
}
A single events collection holds documents from all eight workers. That uniformity is what makes the downstream cascade query trivial — $vectorSearch, $graphLookup, and Atlas $search all read from one collection regardless of whether the event started life as an SEC filing or an earthquake.
The part that took me a while to internalise: the moment the worker calls
await events.update_one(
{"external_id": doc["external_id"]},
{"$setOnInsert": doc},
upsert=True,
)
five different things happen inside Atlas, roughly in that many milliseconds:
- Atlas Automated Embedding runs
voyage-4over thetextfield server-side and writes a 1024-d vector into a hidden field. The worker never touches an embedding API; the vector is there by the timeupdate_onereturns. - The vector index (cosine, 1024-d) incorporates the new embedding — the document is searchable by
$vectorSearchimmediately. - The Atlas Search index over
tickers,entities, andheadlinepicks up the document for exact lexical match. - The TTL index on
ttl_atschedules the document for automatic deletion 14 days out. M0's 512 MB cap is enforced by the database, not by a cron I had to remember to write. - If
impact == "high", the change stream —
db.events.watch([{"$match": {"fullDocument.impact": {"$in": ["high"]}}}])
— wakes the FastAPI SSE endpoint, which pushes the new document to every connected browser as a Server-Sent Event. The globe lights up. No polling, no webhooks, no glue.
The yfinance worker is the one exception. It writes OHLCV bars to a native time-series collection (prices) with timeField: t and metaField: ticker. That's what gives the time-machine its frame-by-frame replay without a paid-tier index.
End-state: eight independent processes, one canonical document, one collection, and every Atlas capability that touches an event wired up at the moment of insert — embedding, vector index, lexical index, TTL, change stream. The reasoning side doesn't have to ask for anything; everything it needs is in place by the time a cascade query fires.
🧠 The brain — a society of agents
If ingestion makes sure every event lands somewhere queryable, the brain turns a query into a cascade. It is not one large LLM call. It's five.
A real research desk isn't one person. It's an analyst, a sceptic, a forecaster, a historian, and somebody whose job is to chase down the actual source. CascadeTerminal's brain is built the same way — five Gemini 3 agents, each with one job, fanned out in parallel.
┌─────────────────────────────────┐
│ Cascade Synthesizer (Gemini 3 Pro)
│ · hybrid search + $graphLookup
│ · MongoDB MCP tools as its hands
└────────────────┬────────────────┘
│
┌────────────────┬──────────────┼──────────────┬────────────────┐
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐
│ Critic │ │ Predictor│ │ Memory │ │Researcher│ │ Performance │
│ G3 Flash │ │ G3 Flash │ │ G3 Flash │ │ G3 Flash │ │ Advisor │
│ challenge│ │ next-hop │ │ analog │ │ evidence │ │ (MCP tool) │
│ weak │ │ moves │ │ search │ │ trail + │ │ │
│ links │ │ + δ-σ │ │ ($vSrch) │ │ citations│ │ │
└─────┬────┘ └─────┬────┘ └─────┬────┘ └─────┬────┘ └─────┬────────┘
│ │ │ │ │
└──────────────┴──────────────┴──────────────┴───────────────┘
│
▼
fan-in → cached cascade document
in MongoDB `cascades` collection
Cascade Synthesizer · Gemini 3 Pro. The only agent that talks to the user's question directly. It calls $vectorSearch and Atlas $search through the MongoDB MCP server, fuses with RRF, walks $graphLookup three hops out from the root ticker, scores candidates with Voyage rerank-2.5, and writes a draft cascade with a one-line why on every edge. Then it fans the draft to the four specialists in parallel.
Critic · Gemini 3 Flash. Asks the most useful question on any research desk: which links are weakest, and what would falsify them? Returns confidence haircuts and counter-arguments.
Predictor · Gemini 3 Flash. Projects each first-order node's next five-day directional move (up / flat / down) with a confidence score, grounded in the relationship type — "TSM is a sole-source foundry; supply shock is mechanically priced within 48 hours." That's the rail of arrows in the cascade tree UI.
Memory · Gemini 3 Flash. $vectorSearch over embedded cascade summaries surfaces structural analogues — "this looks like the March 2011 Tōhoku supply shock to the auto sector."
Researcher · Gemini 3 Flash. Builds the evidence trail. For every edge in the final cascade it goes back to events via $vectorSearch + Atlas $search through the MongoDB MCP server and attaches the concrete source documents — the 8-K paragraph, the news article, the USGS quake record, the GDELT incident — that justify that edge. The cascade becomes auditable.
A society instead of one big call buys two things: specialisation (a focused Flash prompt out-performs a kitchen-sink Pro prompt) and graceful degradation (if any one specialist times out, a deterministic local stub fills that role and the rest of the society still ships). Every agent reaches Atlas through the official MongoDB MCP server — no custom tool wrappers, no raw credentials in LLM context.
🍃 MongoDB — thirteen capabilities, each load-bearing
Both halves of the system terminate on the same Atlas cluster. The rule I held myself to was that every Atlas capability listed has to do real work — pull any one out and a feature breaks.
| # | Capability | Role |
|---|---|---|
| 1 | $vectorSearch |
Semantic recall over events + Memory's historical-analog lookup. |
| 2 | Atlas Search ($search) |
Exact ticker / entity precision in the search bar. |
| 3 | Reciprocal Rank Fusion | $facet-parallel vector + lexical hits fused at query time. |
| 4 | $graphLookup |
The 3-hop cascade walk over 1,149 supplier / customer / peer / sector edges. |
| 5 | $facet |
Parallel sub-pipelines for dashboard stats and hybrid search in one round trip. |
| 6 | Time-series collection | Native OHLCV storage. The yfinance worker writes here. |
| 7 | TTL index | 14-day expiry on events — keeps M0 under its 512 MB cap without a cron. |
| 8 | Change streams | The push side of live updates — no polling, no webhooks. |
| 9 | Atlas Automated Embedding | voyage-4 runs server-side on insert. |
| 10 | Voyage rerank-2.5 |
Cross-encoder reranks cascade candidates by narrative strength, not cosine distance. |
| 11 | voyage-multimodal-3.5 |
Chart and PDF embeddings for the multimodal endpoint. |
| 12 | MongoDB MCP server | The agent society's tool layer. Every sub-agent talks to Atlas through MCP. |
| 13 | Performance Advisor | Surfaced live in the UI — the system tells you which index would make it faster. |
I started the project assuming I'd need a graph database, a vector store, and a search engine. Atlas does all three in a single aggregation pipeline. The architecture got smaller the more I leaned in.
⚠️ What almost broke
- Three Atlas Search indexes, three jobs. M0 caps you at three. I needed ticker precision, semantic recall, and a hybrid scoring path. Fusing them at query time with
$facet+ RRF (instead of using the fourth index slot I didn't have) ended up being the right architecture anyway. - Tickerless events broke the model. A typhoon has no
$NVDA. The whole click ticker → walk graph assumption falls over. The Coordinate Mapping System — Gemini 3 Pro JSON-mode, schema-validated regions and sectors, server-side lat/lon validation, globe arcs — was the hardest single feature to ship and the one I'm proudest of. - A 3D globe is a UX trap. Auto-orbit is delightful for four seconds and infuriating on second five when you're trying to click Taiwan. I added an interaction guard that suspends orbit the instant a user touches the canvas and resumes only after the cascade closes.
- Cold start vs change streams. Cloud Run scales to zero, but a change stream needs a long-lived connection. The fix: a separate SSE service, a 15-second heartbeat to keep intermediaries from killing the connection, and a backfill query on every fresh client connect so the globe is never blank.
- Five agents, one rate budget. Five Gemini 3 calls per cascade against a tight RPM cap meant the orchestrator had to fan out in parallel and tolerate any one specialist falling back to a local stub. That fallback path is what makes the demo reliable.
✍️ What I learned
- Graph + vector + lexical in one query, on one cluster, is genuinely different. I assumed I'd need a graph DB, a vector store, and a search engine. Atlas collapses all three. Each capability you stop reaching for is a system you don't have to operate.
- A society of small specialists beats one large generalist. Five focused Gemini 3 prompts in parallel produced a better cascade than one giant prompt that tried to do all five jobs. They were also individually fixable — when a prompt went off, I could replace one without retesting the others.
- Treat every model output as untrusted input. Gemini 3 will occasionally hand you
lat = 91.4. Pydantic at the boundary, server-side range checks, drop-and-log on failure. The Coordinate Mapping System works because I never trust it. - Graceful degradation matters more than clever orchestration. When Voyage throttles, the cascade still renders without rerank. When a specialist times out, the society shows a local fallback. The system never blocks on its weakest dependency.
🎯 What's next
The most interesting next step isn't a feature — it's a role switch. CascadeTerminal currently consumes the MongoDB MCP server. I want to ship an MCP server for CascadeTerminal, so Claude, ChatGPT, in-house copilots, and other agents can ask CascadeTerminal questions the same way CascadeTerminal asks Atlas questions today.
CascadeTerminal-as-infrastructure. That's the arc.
Solo build. Open source under Apache-2.0. Every URL in this submission is live.
Built With
- aisstream
- alpha-vantage
- atlas-search
- atlas-vector-search
- change-streams
- cloud-run
- cloud-scheduler
- docker
- fastapi
- finnhub
- framer-motion
- gdelt
- gemini
- gemini-3-flash
- gemini-3-pro
- google-adk
- google-cloud
- google-secret-manager
- marketaux
- mcp
- mongodb
- mongodb-atlas
- motor
- next.js
- noaa
- opensky
- pydantic
- python
- react
- react-globe.gl
- rss
- sec-edgar
- shadcn-ui
- sse
- tailwindcss
- three.js
- typescript
- usgs
- vercel
- vertex-ai
- voyage-ai
- voyage-multimodal-3.5
- voyage-rerank-2.5
- yfinance
- zustand
Log in or sign up for Devpost to join the conversation.