A live wiki for contested facts. Instead of hiding disagreement behind a single neutral narrative, it surfaces it.
Wikithon 2026 submission · AI industry knowledge graph
Most wikis flatten the web into one voice. ConsensusWiki keeps every source's voice intact and shows you exactly where they agree, where they collide, and where a claim is standing alone. Every claim is cited, every contradiction is rendered side-by-side with an LLM-judged rationale, and every entity has a live knowledge graph that grows as you ingest more sources.
| What it is | A real-time, multi-source wiki that highlights disagreement instead of erasing it. |
| Demo topic | AI industry news (model releases, funding rumors, benchmark disputes). |
| How it works | URL → fetched + normalized → HydraDB indexes + builds a knowledge graph → NVIDIA NIM extracts atomic claims → pairwise contradiction judgement → Postgres derives the structured wiki view. |
| Why HydraDB earns its keep | We use graph_context.query_paths, chunk_relations, sub-tenant isolation, recency_bias, and signed webhooks — features RAG-on-vector-DB demos don't have. The knowledge graph is read directly from Hydra; we don't run a separate graph DB. |
| Routes | / dashboard · /ingest paste a URL · /wiki/[entity] entity page · /graph topic graph · /query ask the wiki · /wiki/q/[slug] saved query with numbered citations + Connections-Used view. |
| Stack | Next.js 16 App Router (RSC + Cache Components), Vercel (Functions + Webhooks), Neon Postgres, HydraDB, NVIDIA NIM (Llama-3.1), Tailwind v4, shadcn/ui, cytoscape. |
Wikipedia gives you a single agreed-upon truth. The web gives you ten contradictory ones. Most AI tools paper over that gap — they pick a narrative or hedge so heavily nothing useful comes out. ConsensusWiki keeps the disagreement.
Ingest a TechCrunch article. The pipeline extracts atomic claims, finds every other claim about the same entity, and asks an LLM: do these agree, contradict, qualify, or stand alone? The entity page reshapes itself: Established (multiple sources align), Contested (sources collide, shown side-by-side with rationale), and Single-source (one outlier waiting for corroboration). The topic graph grows — red edges mean dispute, green means corroboration, dashed means a source mentioning an entity.
Ask the query box a question. The answer comes back with inline numbered citations and a Connections Used sub-graph showing exactly which entities and relations from HydraDB informed it. Click any number, it scrolls to the source.
That's the loop. Everything else is plumbing.
┌─────────────────────────────────────────────────────────────────────┐
│ Browser (Next.js 16 RSC + Cache Components) │
│ / /ingest /wiki/[entity] /wiki/q/[slug] /graph /query │
└─────────────────────┬───────────────────────────────────────────────┘
│ Server Actions
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Ingest Workflow (src/lib/ingest-workflow.ts) │
│ fetch + normalize → Hydra upload → poll → extract claims │
│ → canonicalize entities → judge contradictions → lede synthesis │
│ → revalidate cache tags │
└──────┬─────────────────────────┬──────────────────────────┬─────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ HydraDB │ │ NVIDIA NIM │ │ Neon Postgres │
│ - upload │ │ - extract │ │ - sources │
│ - recall │◀────────│ - canonicalize │───────▶│ - entities │
│ - graph │ │ - judge │ │ - claims │
│ context │ │ - lede │ │ - relations │
│ - webhook │────────▶│ - query │ │ - saved_queries│
│ →status │ │ synthesis │ │ (graph_ctx) │
└─────────────┘ └─────────────────┘ └─────────────────┘
│ ▲
│ POST /api/webhooks/hydra (HMAC-signed) │
└────────────────────────────────────────────────────┘
indexing.status_changed → updateSourceStatus()
- HydraDB — raw chunks, embeddings, knowledge graph (entity-relation triplets), full recall. Source of truth for chunk excerpts + multi-hop graph paths.
- NVIDIA NIM — all LLM work behind
src/lib/llm.ts: claim extraction, entity canonicalization (with model-family alias generation), pairwise contradiction judgement, entity lede synthesis, query answer synthesis with inline citations. - Neon Postgres — derived application data (sources, entities, aliases, claims, claim_relations, ledes, saved_queries with persisted graph_context). Stable SHA-256 ids make every write idempotent.
- Vercel — Functions for the ingest workflow + webhook receiver. Cache Components with
cacheTag/cacheLife. Webhook tunnel via ngrok during the hackathon.
Four Hydra features make this app possible:
graph_context.query_paths+chunk_relationsreturn knowledge-graph triplets at query time. We pipe these directly into the Connections-Used graph on/wiki/q/[slug]— no separate graph database, no manual entity extraction at recall time.- Sub-tenant isolation (
sub_tenant_id = wikithon-<topic-id>) gives us per-topic universes for free. Adding a second topic is a one-line change. recency_bias: 0.6keeps entity pages fresh as new sources arrive without us building any decay logic.- Webhooks (
indexing.status_changed) flip ourhydra_statuscolumn the instant a file finishes indexing. No polling cron, no stale UI.
Plus: chunk-level citations. When the entity page renders, we call recallEntityContext(canonicalName), map returned chunks back to their source rows, and inline the actual quoted excerpt next to each claim. Real "press → highlight in the source" UX without any extra storage.
src/lib/ingest-workflow.ts:runIngestWorkflow
| Step | What | Failure mode |
|---|---|---|
1. fetchAndNormalize |
fetch() with realistic Chrome UA + 15s AbortController. Readability extract. Falls back to r.jina.ai proxy on 403/429/5xx or network error. |
workflow_status = failed_fetch |
2. hydraUpload |
Multipart POST /ingestion/upload_knowledge with tenant_id, sub_tenant_id, app_knowledge[{...}]. Idempotent via stable source.id = sha256(topic|url). |
workflow_status = failed_upload |
3. pollHydraStatus |
10s ceiling. Hydra failure no longer blocks local pipeline (log + continue). Background webhook will flip hydra_status later. |
hydra_status = errored, workflow proceeds |
4. extractClaimsStep |
NIM JSON-mode prompt → Zod-validated ClaimExtractionSchema. Subjectless sentences in the deterministic fallback get the inferred entity prefixed ("released GPT-5.5..." → "GPT-5.5 released..."). |
Falls back to regex-based extractor |
5. canonicalizeEntities |
Batch NIM call returning {raw, canonicalName, entityType, aliases[]}. Deterministic GPT-N family-alias generation in the fallback path so /wiki/gpt-5 resolves to the GPT-5.5 Instant entity without any manual DB row. |
Per-entity normalization heuristics |
6. judgeContradictionsStep |
For each new claim, fetch the entity's existing claims, pairwise NIM judgement → agree | contradict | qualify | unrelated with rationale + confidence. Internal-fallback rationales are filtered out at render time so users never see "fallback used" copy. |
Per-pair failure isolated; one bad pair doesn't kill the step |
7. synthesizeLedesStep |
One NIM call per touched entity, throttled 750ms between calls. Circuit-breaks on first 429 to avoid burning quota across remaining entities. | Per-entity errors swallowed with console.warn |
8. invalidateCacheStep |
revalidateTag('entity:<id>', 'max'), revalidateTag('topic:<id>'), revalidateTag('graph:<topic>'), revalidatePath('/ingest'). |
Best-effort; pages go stale at most until next request |
Throughout: safeUpdateWorkflowStatus records pending → extracting → judging → complete so the /ingest dashboard shows real progress. safeUpdateHydraStatus writes the Hydra side independently — the two columns are decoupled.
Two orthogonal state machines per source:
workflow_status: pending → extracting → judging → complete
└→ failed_fetch / failed_upload
hydra_status: queued → in_progress → success / errored / unknown
Hydra being slow no longer blocks the local pipeline. The UI surfaces both as separate pills with distinct treatments. Stale pending sources (>5 min) get a "Force retry" affordance. Webhook updates flip hydra_status in real time without page reloads needed.
Each route is a deliberate slice of the workflow. The order below mirrors the natural usage flow: ingest sources → explore an entity → see the topic graph → ask a question → revisit a saved answer.
Purpose. Single-glance read on the corpus. Tells you how rich the topic universe is right now and where the disagreement lives.
What you see.
- Big headline and color-coded CTAs into
/ingestand/graph. - Topic stats card on the right: entity count, claim count, source count, plus a destructive-tinted Contradictions row counting deduped contradict pairs (not relations — one disputed claim no longer inflates the number).
- Three explainer cards: Established · Contested · Single-source — these are the buckets every entity page uses.
- Entities list — top 12 entities ranked by
claimCount + contestedCount × 3. The ranking heuristic pushes the most-debated entities to the top. Each row is a link →/wiki/[entity]. Red badge on the right shows contested-claim count when > 0. - Recent sources — last 6 ingested URLs with the shared
<StatusPill>showing both workflow and Hydra status.
How to use it.
- Land here first. Pick an entity that has a contested badge to see the most interesting page.
- Watch the contradictions number tick up after a fresh ingest.
Purpose. Add new sources to the topic and monitor the pipeline live.
What you see.
- Ingest source card (top): paste an article URL or attach a PDF, hit Queue ingest. Button spins ("Queuing…") while the server action runs.
- Ingest log (below): every source ever ingested, newest first. Each row shows:
- Title + publisher + published date.
- Dual StatusPill stack: top pill is local workflow state (
pending → extracting → judging → completeorfailed_fetch / failed_upload), bottom pill is Hydra indexing state (queued / in_progress / success / errored / unknown). - Refresh icon next to the Hydra pill: calls the
recheckHydraserver action, hits Hydra'sverify_processingendpoint, updates the DB. - 4-step workflow timeline showing where the pipeline is (fetch · upload · poll · claims+graph). Active step pulses, failed steps go red, completed steps turn emerald.
- Retry banner when applicable. Red banner + "Retry failed step" for hard failures. Amber banner + "Force retry" when a source has been stuck in
pendingorextractingfor >5 minutes.
- Header chips summarize the log:
N complete,N in flight,N failed.
How to use it.
- Paste any article URL. Watch the timeline animate. Workflow finishes locally in ~5 seconds — Hydra indexing finishes in the background and pings us via webhook when done.
- For demo: ingest one URL live in front of judges. Show the timeline. Show the badge flip from
queued→successvia webhook with no page reload. - If a source has been stuck for hours, hit Force retry — it re-runs the local pipeline immediately and resets Hydra to
queuedso the next webhook tick updates it.
Purpose. The actual wiki page. Everything we know about one entity, sliced by how reliable it is.
What you see.
- Title + type badge (PERSON, ORG, MODEL, PRODUCT, EVENT).
- Lede — a Sparkles-marked callout block at the top with the LLM-synthesized summary plus
Synthesized · N sourcesmeta. Falls back to a dashed empty state when no lede has been generated yet. - Contested claims (red, top of the page) — claim cards rendered side-by-side with their opposing source. Each card has:
- Stance badge (
factual / opinion / prediction / leak / rumor) + confidence percentage. - Claim text.
- Source excerpt — actual quoted chunk pulled from HydraDB recall (when indexed). Real "press → see the underlying quote" UX. Falls back to "Citation chunk pending" placeholder when Hydra hasn't indexed the source yet.
- Publisher + linked article title.
- Rationale banner showing the LLM's reasoning for why the claims contradict (internal fallback rationales filtered out so users only see real explanations).
- Stance badge (
- Established claims — claims with ≥2 supporting sources or an
agreerelation. Two-column grid. - Single-source claims — useful but isolated, waiting for corroboration. Two-column grid.
- Related evidence — claim-pair diffs from the relations table for this entity (not just contradicts — qualifies and agrees too).
- Timeline — every source mentioning this entity, ordered by publish date.
How to use it.
- Click any entity from the dashboard list.
- Reach a page directly:
/wiki/openai,/wiki/anthropic,/wiki/gpt-5-5-instant. - Aliases work:
/wiki/gpt-5resolves to the GPT-5.5 Instant entity (model-family alias generation). - Unknown slug → renders a friendly "Entity not found" page with links back to the dashboard and ingest.
Purpose. Visualize the whole topic as a network. See which entities cluster, which sources mention what, and where the disputes are.
What you see.
- Cytoscape canvas (concentric layout, node colors keyed to entity type):
- Blue = MODEL, Purple = ORG, Green = PERSON, Orange = PRODUCT, Magenta = EVENT, Slate rectangle = SOURCE.
- Node size scales with claim count.
- Edges: Red = contradict, Green = agree, Amber = qualify, Gray dashed = source mentions entity.
- Legend bar below the canvas mapping colors to entity types and edge styles.
- Edges table at the bottom, sorted by impact: contradict → qualify → agree → mentions. Source-mention rows (the noisy ones) collapsed behind a
<details>disclosure. - Source / target labels truncated with full title on hover.
How to use it.
- Pan and zoom freely.
- Click a node to focus (cytoscape default). The colors make the dispute hotspots obvious — anywhere two big nodes are joined by a red edge is a place to dig in.
- Scan the Edges table for the most impactful relations first.
Purpose. Ask an open question against the corpus and get a synthesized answer with citations.
What you see.
- Ask the wiki card on the left: question textarea + Ask button. The button disables and spins ("Thinking…") while the server action runs.
- Recent saved queries on the right: last 8 questions you've asked, each linking to its saved page.
How it works under the hood.
- Server action
askQuestionloads up to 20 candidate sources from Postgres. - Calls Hydra
fullRecall(question)to fetch graph context (knowledge-graph triplets surrounding the question). - Sends the question + numbered candidate sources to NIM with instructions to write the answer with inline
[N]markers and acitedSourceIdsarray. - Validates cited ids against the candidate set (drops hallucinations) and renumbers
[N]markers post-hoc so they always run 1..N matching the citation order. - If Hydra returned no triplets (free-tier backlog), falls back to building a graph from our Postgres
claim_relationstable for the cited sources so the Connections Used view always has something to show. - Saves the row to
saved_querieswith the full graph_context JSONB and redirects to/wiki/q/<slug>.
Suggested questions for the AI-industry demo corpus:
- "How is Anthropic competing with OpenAI in enterprise?"
- "How much money has Anthropic raised in 2026 and at what valuation?"
- "Did GPT-5.5 ship as a real release or as a limited rollout?"
- "Who is behind Recursive Superintelligence?"
- "What AI safety concerns are tied to self-improving systems?"
Purpose. The persisted view of a single query — answer + citations + the knowledge-graph trace that produced it.
What you see.
- Answer card with the question as the title, badge "Saved query", and the synthesized markdown body. Inline
[N]references are clickable<sup>anchors — clicking scrolls down to the matching numbered citation card. - Connections used card with a source badge:
- HydraDB graph — triplets came from Hydra's
graph_context.query_paths+chunk_relations. Predicates look likeRELATED_TO / FOUNDER_OF / DEVELOPED_BY. Nodes are canonical entity names. - Local fallback — Hydra returned nothing, triplets came from our Postgres
claim_relationstable. Predicates arementions / agree / contradict / qualify. Nodes are source titles + entity names. - Cytoscape mini-graph showing the triplets with type-colored nodes + edge labels.
- Triplet rows below:
source → PREDICATE → targetwith chunk context excerpt when available. - 1-step / Multi-step toggle appears only when Hydra returns mixed hops. Single-class data hides the toggle and shows a count chip instead.
- HydraDB graph — triplets came from Hydra's
- Citations card with numbered circular badges (
1,2,3…) matching the inline[N]anchors. Each row: source title (clickable to original article), publisher, publish date, Hydra status badge.
How to use it.
- Click any
[N]in the answer body → page scrolls to the matching citation card (#cite-Nanchor). - Look at the source badge on Connections used to know whether Hydra's knowledge graph informed the answer or whether the local Postgres graph filled in.
- Bookmarkable URL: every saved query has a stable slug, share with anyone.
vs. ChatGPT / Perplexity — they synthesize one answer and hide the conflict. We render both sides with rationale. Every claim is traceable to a source by chunk-level excerpt.
vs. NotebookLM — single-document focus, no multi-source reconciliation, no graph view, no contradiction surfacing.
vs. Wikipedia — single editorial voice + edit wars buried in talk pages. We surface disagreement as first-class UI.
vs. RAG-over-vector-DB hackathon projects — we exploit the graph features of HydraDB (graph_context, query_paths, chunk_relations), not just embedding search. The Connections-Used view is real graph data, not a synthesized diagram.
git clone <repo>
cd wikithon2026
pnpm install
cp .env.example .env.local # fill in DATABASE_URL, HYDRA_API_KEY, NIM_API_KEY
pnpm db:migrate # applies 0001..0003 idempotently
pnpm dev # http://localhost:3000| Var | Source |
|---|---|
DATABASE_URL |
Neon (via Vercel Marketplace or direct) |
HYDRA_API_KEY |
HydraDB |
HYDRA_TENANT_ID |
HydraDB |
HYDRA_BASE_URL |
https://api.hydradb.com |
NIM_API_KEY |
NVIDIA NIM |
NIM_BASE_URL |
https://integrate.api.nvidia.com/v1 |
NIM_MODEL |
meta/llama-3.1-8b-instruct |
HYDRA_WEBHOOK_ENABLED |
1 to enable /api/webhooks/hydra |
HYDRA_WEBHOOK_SECRET |
HMAC signing secret (≥16 chars; generate with openssl rand -hex 32) |
pnpm lint && pnpm test && pnpm build
# 40 unit/integration tests, lint clean, build clean
pnpm test:e2e
# 3 Playwright specs pass + 1 skip (query test gates on NIM_API_KEY)After the sub-tenant-routing fix, re-upload every existing source so chunks land under the right sub-tenant:
pnpm hydra:reingest # all rows
pnpm hydra:reingest <url> # just oneOnce HYDRA_WEBHOOK_ENABLED=1 and the app is reachable on a public URL:
curl -X POST https://api.hydradb.com/webhooks/indexing \
-H "Authorization: Bearer $HYDRA_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"url\": \"https://your-app.example.com/api/webhooks/hydra\",
\"event_types\": [\"indexing.status_changed\"],
\"signing_secret\": \"$HYDRA_WEBHOOK_SECRET\"
}"src/
app/
page.tsx # dashboard
ingest/ # /ingest + server actions (retry, recheck)
graph/page.tsx
query/ # /query + askQuestion action
wiki/[entity]/ # entity page + not-found
wiki/q/[slug]/ # saved query page with Connections Used
api/webhooks/hydra/route.ts # HMAC-verified Hydra webhook
components/
graph/topic-graph.tsx # cytoscape topic graph
connections-used.tsx # client-side Connections Used view
status-pill.tsx # shared workflow + hydra pill cluster
ui/ # shadcn primitives
lib/
ingest-workflow.ts # the pipeline
hydra.ts # upload + recall + verify_processing
llm.ts # NIM client + all prompts + Zod schemas
recall.ts # entity chunk fetch + graph extractors
store.ts # Postgres + memory store split
app-service.ts # store wrappers + cache invalidation
normalize-source.ts # Readability + Jina fallback
types.ts # shared types
demo-data.ts # deterministic demo seed
utils.ts # cn + slugify
db/
migrate.ts # iterates db/migrations/*.sql in order
migrations/
0001_consensuswiki.sql # base schema
0002_workflow_status.sql # workflow / hydra status split
0003_saved_query_graph_context.sql # persist Hydra graph_context per query
scripts/
reingest-hydra.ts # bulk re-upload existing sources
tests/
unit/ # Vitest: hydra, llm, normalize-source, data
integration/ # store + workflow + ingest-action
e2e/ # Playwright smoke
docs/specs/ # design spec
docs/superpowers/plans/ # implementation plans + audit + handoffs
- Full ingest pipeline (fetch → normalize → Hydra upload → poll → claim extract → canonicalize → contradiction judge → lede synthesize → revalidate)
- Stable SHA-256 ids; idempotent inserts
- Three-bucket claim grouping with side-by-side contested cards
- Cytoscape topic graph with type-colored nodes, edge ranking, table fallback
- Inline numeric citations on saved queries with anchor scroll
- Connections Used view backed by Hydra
graph_context(with Postgres fallback when Hydra has nothing yet) - Real-time Hydra status via signed webhooks
- Per-row Hydra recheck + workflow retry buttons
- Model-family alias generation
- E2e suite stable against live data (no demo-seed dependency)
- 40 unit/integration tests, lint+build clean
Pulled from the design spec and our own backlog:
- Vercel Workflow (WDK) — replace
after()fire-and-forget with durable step execution + retry semantics + step UI. - PDF uploads —
normalizePdfis a stub; wirepdf-parseagainst Vercel Blob. - Vercel Runtime Cache for
fullRecall— 5-min TTL keyed by(query_hash, sub_tenant)to cut Hydra quota use. - Backfill
claims.chunk_uuidfrom recall — direct chunk anchors per claim instead of best-match by source id. - Lede regen heuristic — regenerate only when source count grows ≥3 since
source_count_at_gen. - NIM warm-up ping in
instrumentation.tsto prevent cold-start jitter on stage. - OpenRouter wrapper behind an env flag for multi-LLM fallback.
- Publisher bias chip on cited sources.
- RSS poller for continuous topic ingest.
- Admin entity-merge UI for manual canonicalization fixups.
- HydraDB — knowledge graph + recall + webhooks. The
graph_contextfeatures are why this app exists. - NVIDIA NIM — every LLM call.
- Vercel — hosting target (Functions + webhooks + Next.js 16).
- Neon — Postgres via the Vercel Marketplace.
- shadcn/ui, cytoscape, Tailwind v4, lucide-react — UI plumbing.
MIT (or as configured in LICENSE). Hackathon submission code, use freely.