Inspiration
Dialektika started from one conviction: putting two strong models against each other produces a more crystallized point than asking one model or reading two answers side by side. Ask a single model and it mostly agrees with you. Compare two answers and you get two monologues that never touch. Neither actually tests an idea.
The trigger was the everyday version of that: open ChatGPT, Claude, and Gemini in separate tabs, copy-paste each reply into the next, and still end up with two one-sided answers that never clash. We wanted the one thing that was missing a place where model B directly rebuts what model A just said, the argument evolves over rounds, and a judge tells you where the standpoints actually land. It's a tool for constructive learning: argue a belief until you can see its real shape, instead of accepting the first model that nods along.
What it does
Dialektika puts two AI models in a single arena to argue opposite sides of a claim.
- Pick two models by name from a searchable list of OpenRouter's models, with a manual model-id fallback if the list can't be fetched.
- Assign a defender and an opposer, name a topic, and choose the number of rounds (default 3).
- Watch a real, evolving debate in a two-column arena. The defender opens, the sides alternate, and each turn streams in token by token, every reply answer directly rebutting the one before it.
- Optionally let Jev judge. Jev scores each side on reasoning, engagement, and burden of proof, names the stronger side, cites the decisive turns, and closes with a crystallization.
- Revisit past debates from an in-browser history list, and download any debate as a
.docxfor Word or Google Docs.
No logins, no database, no server to keep online, it runs locally as a single Next.js app.
How we built it
Dialektika is a local Next.js 16 (App Router) app in TypeScript and React 19. The whole thing hangs off one idea: a referee that runs two models one after another and merges their turns into a single ordered stream.
- The setup screen fetches OpenRouter's model list once from
GET /api/modelsand fills the pickers. - On Start, the browser
POSTs the config to/api/debateand opens a stream. - That route is the referee: it runs the debate loop, asks the next model to argue through the Vercel AI SDK → OpenRouter, and streams each turn back tagged with its side and round as NDJSON.
- The arena drops each chunk into the matching column of the two-column view.
- When the last round ends, Jev runs (if enabled) and streams a verdict.
- The finished debate is written to
localStorageunderdialektika.debates.v1.
The sequencing rules live in a pure, tested module (lib/orchestrator.ts), who speaks next, what history they see, when to stop. Jev uses the AI SDK's structured output against a Zod schema, so the verdict is real typed data rather than parsed prose. Scores are averaged per side across three axes reasoning $s_r$, engagement $s_e$, and burden of proof $s_b$:
$$S = \frac{s_r + s_e + s_b}{3}$$
On a level score the opposer wins, because the defender failed its burden of proof.
Challenges we ran into
- Merging sequential streams. The core risk was running two models turn by turn and still delivering one correctly ordered stream to the browser. We solved it with a pure orchestrator plus a tagged NDJSON protocol, and pinned it down with an integration test that chains two sequential
streamTextcalls and asserts turn order survives. - The AI SDK moving under us. AI SDK v7 rejects a
systemmessage placed insidemessages, so we moved the debate system prompt into the SDK'sinstructionsoption. - Silent failures. Consuming
textStreamsilently swallowed provider errors, so a failed turn looked like a successful empty turn. We switched tofullStreamto surface the failure and render it inline with a Retry. - A judge with no model to call. The plan only defined the two debater pickers, so Jev had no third model. We added an environment-overridable
JUDGE_MODEL(defaultopenai/gpt-4o-mini). - An API we had to verify, not guess. The structured-output surface (
generateObjectvsOutput.object) had changed; we confirmed the current shape before writing it. The model picker also first read as an empty, broken input, so we rebuilt it as a dropdown with an in-panel search box and a clear loading state.
Accomplishments that we're proud of
- A debate that genuinely evolves: model B rebuts model A's last turn, live, in one screen, instead of two answers sitting side by side.
- Jev producing a defensible call, per-axis scores, the stronger side, the cited decisive turns, and a crystallization rather than a bare scoreboard.
- Building the whole thing test-first: pure orchestrator tests, a stream-protocol round-trip, a storage round-trip, and the merged-stream integration test that retires the project's one unfamiliar risk.
- Making failures visible, a missing key, a bad model id, or a dropped call surfaces clearly, with a Retry that picks the run back up instead of hanging.
- Shipping the full core loop end to end: setup → live debate → verdict → history →
.docxexport.
What we learned
- How to drive sequential, provider-backed streaming through the Vercel AI SDK and reassemble it into one ordered stream without losing turn boundaries.
- That structured output is a different discipline from parsing free text — a Zod schema turns the judge into a typed function instead of a hopeful regex.
- The difference between the SDK's
textStreamandfullStream, and how easy it is for a real failure to hide behind an "empty" success. - That keeping the sequencing logic pure made the hardest part of the app the easiest part to test.
- A new building paradigm: this project was built interview-first, with the direction drawn out of our own reasoning instead of a single long upfront prompt — and it produced a sharper plan for it.
What's next for Dialektika
Dialektika today is a focused proof of concept: one local user, no auth, no hosting. Next we'd like to:
- Direct Google Docs integration so debates land straight in a doc pane (
.docxalready covers the re-read need today). - A richer history — search, tags, and sorting beyond the simple list.
- Sharing a debate as a link, so a clash can be sent to someone else.
- Deeper debate configuration — speech types and timed phases beyond a plain round count.
- More than two sides, and eventually tournament brackets.
Built With
- css
- docx
- html
- javascript
- nextjs
- node.js
- openrouter
- react
- typescript
- vercel
- vitest
- zod
Log in or sign up for Devpost to join the conversation.