Autonomous multi-agent research and verification. Give it a claim or a question. Four agents argue it out live: one breaks the query apart, one scrapes the web, one builds a knowledge graph, one grades the evidence and sends the swarm back to work if it isn't convinced.
No canned demo data. No single LLM call pretending to be "research." A cyclic LangGraph workflow that re-searches itself until confidence clears 80% or it runs out of cycles, streamed to the browser over a WebSocket as it happens.
Built by Dhruv Tibarewal.
Live demo: counterpoint-engine.vercel.app — frontend on Vercel, talking to a FastAPI + Celery backend on Render's free tier at counterpoint-engine-api.onrender.com. Free-tier backend spins down after 15 minutes idle, so the first request after a while takes 20-50s to wake up -- the UI shows this explicitly instead of looking broken.
Submit a query. The Orchestrator decomposes it into sub-tasks and search queries. The Scraper runs a headless Playwright session against each query and pulls full page text from every result. The Graph Builder extracts entities and relationships from that evidence and writes them into Neo4j, streaming nodes into the frontend's WebGL graph as they're found. The Critic reads the accumulated evidence, scores confidence from 0 to 1, and decides: verified, disputed, or insufficient evidence. Below 80% confidence, it names exactly what's missing and the graph loops back to the Orchestrator with that gap instead of repeating the same search.
Every step is a line in the Agent Terminal. Every entity is a node in the graph the moment it's extracted. Nothing is precomputed.
Two example queries to throw at the live demo:
Did the 2024 Nobel Prize in Physics go to work on neural networks?-- narrow, checkable, resolves fast.Is Mount Kilimanjaro the tallest freestanding mountain in the world?-- a claim with a common misconception baked in, good for watching the Critic push back and trigger a re-search cycle.
Submit either one, watch the Agent Terminal narrate each step, and watch the knowledge graph populate node-by-node as the Graph Builder extracts entities.
flowchart TD
UI["Next.js App"]
subgraph Render["Render — Backend"]
API["FastAPI + Celery"]
Redis[("Redis")]
end
subgraph Data["Data Stores"]
Neo4j[("Neo4j AuraDB")]
Qdrant[("Qdrant Cloud")]
end
Groq{{"Groq API"}}
Web[["Live Web"]]
UI -- "query / WS" --> API
API --> Redis
Redis --> API
API --> Groq
API --> Web
API --> Neo4j
API --> Qdrant
FastAPI and the Celery worker are still architecturally decoupled processes that only communicate through Redis (job state, agent events, graph snapshots) -- that's what makes it possible to reconnect a dropped WebSocket and replay the last 200 buffered events instead of losing progress. In production they happen to run inside the same container, because Render's free plan doesn't offer a separate Background Worker service type (see backend/Dockerfile). If you're on a paid Render plan, split them into two services for better isolation -- render.yaml has a comment showing exactly how.
stateDiagram-v2
[*] --> Orchestrator
Orchestrator --> Scraper: sub-tasks + search queries
Scraper --> GraphBuilder: scraped evidence
GraphBuilder --> Critic: entities + relationships
Critic --> Orchestrator: confidence < 80% and cycles remain
Critic --> [*]: confidence >= 80% or cycles exhausted
| Agent | Job |
|---|---|
| Orchestrator | Breaks the query into 2-5 sub-tasks and 2-6 search queries. On a re-search cycle it reads the Critic's missing_evidence and narrows in instead of repeating itself. |
| Scraper | Runs each search query through DuckDuckGo's HTML endpoint (no API key), then fetches full readable text from every unique result with headless Chromium. |
| Graph Builder | Extracts entities and relationships from fresh evidence via structured LLM output, upserts them into Neo4j, and pushes an incremental snapshot the frontend renders node-by-node. |
| Critic | Scores confidence against the evidence, assigns a verdict, and decides whether another cycle is worth running. |
Confidence threshold, max cycles, and every model name are environment variables — see backend/.env.example.
Backend — FastAPI, LangGraph (cyclic StateGraph), Celery + Redis, Playwright, Neo4j, Qdrant, fastembed (local CPU embeddings, no extra API key), Groq (openai/gpt-oss-120b for orchestration/critique, llama-3.1-8b-instant for extraction) via langchain-groq.
Frontend — Next.js 16 (App Router, TypeScript), Tailwind CSS v4, Framer Motion, Lenis smooth scroll, react-force-graph-2d (canvas knowledge graph), a custom WebSocket hook with exponential-backoff reconnection for Render's free-tier cold starts.
Infra — Docker Compose for local Redis/Neo4j/Qdrant, render.yaml (API service + Redis; the Celery worker runs inside the API container on Render's free plan, which has no separate Background Worker tier), frontend/vercel.json.
You need Docker, Node 22+, and Python 3.12+.
# 1. Backend env
cp backend/.env.example backend/.env
# fill in GROQ_API_KEY — free key at https://console.groq.com/keys
# 2. Frontend env
cp frontend/.env.local.example frontend/.env.local
# 3. Bring up Redis, Neo4j, Qdrant, the API, the worker, and the frontend
docker compose up --buildThe frontend is at http://localhost:3000, the API at http://localhost:8000/docs, Neo4j Browser at http://localhost:7474.
Running the backend outside Docker:
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
playwright install chromium
uvicorn app.main:app --reload &
celery -A app.celery_app worker --loglevel=infoRunning the frontend outside Docker:
cd frontend
npm install
npm run dev# Backend — 23 tests, mocked LLM/scraper/stores, no live services required
cd backend && pip install -r requirements.txt && pytest
# Frontend — typecheck + lint + production build
cd frontend && npm install && npm run lint && npm run build** Note: Cold starts **
Render's free tier spins the web service down after 15 minutes idle. The first request after that takes 20-50 seconds to wake up , the frontend's ColdStartOverlay shows this state explicitly instead of looking broken, and the WebSocket hook retries with exponential backoff until the connection lands.
counterpoint-engine/
├── backend/
│ ├── app/
│ │ ├── agents/ # LangGraph state, 4 agents, graph wiring
│ │ ├── services/ # LLM, scraper, vector store, graph store, event bus, job store
│ │ ├── models/ # Pydantic schemas shared across API/agents/WS
│ │ ├── api/routes.py # REST endpoints
│ │ ├── main.py # FastAPI app + WebSocket endpoint
│ │ ├── celery_app.py
│ │ └── tasks.py # Celery entrypoint that runs the swarm
│ ├── tests/ # 23 tests, no live services required
│ ├── Dockerfile
│ └── requirements.txt
├── frontend/
│ ├── app/ # Landing page + /session/[jobId]
│ ├── components/ # KnowledgeGraph, AgentTerminal, ColdStartOverlay, ...
│ ├── lib/ # WebSocket hook, API client, Lenis provider, types
│ ├── Dockerfile
│ └── vercel.json # Lives here, not the repo root -- Vercel's Root
│ # Directory is set to frontend/ for this project
├── docker-compose.yml
└── render.yaml
This project was made by me to figure out a reliable way to get accurate information .The project is currently under development and scoped with features on the way. Please raise issues and feature suggestions are welcome and will be acknowledged . Let's make it into something that bridges the current gaps.
MIT — see LICENSE.