Simulate a decision before you make it. Then put the simulation on trial.
Describe a real-world scenario. CONCLAVE builds a population of simulated stakeholders, runs them forward through time on real news, then convenes five adversarial AI analysts to cross-examine the result into a calibrated verdict β a probability, a confidence level, and the strongest case that it's wrong.
Runs entirely on free-tier LLM APIs.
React 19 Β· FastAPI Β· PostgreSQL 16 Β· Redis 7 Β· Docker
- The idea
- How a scenario runs
- The Field β agent-based simulation
- The Council β five analysts, one verdict
- After the verdict
- Running entirely on free models
- Architecture
- Bento Terminal β the design system
- Quick start
- Configuration
- End-to-end tests
- Local development
- Project layout
- Documentation
- Status
Ask an ordinary chatbot "will this industrial park get built?" and it gives you a confident paragraph assembled from nothing. It has no model of the fishermen who would lose the bay, no sense of how the council's position drifts once the first survey vessel shows up, and no incentive to tell you where its own answer is weak.
CONCLAVE refuses to answer the question directly. Instead it does two things in sequence, and the second one is adversarial by construction:
-
It simulates. Who is actually affected? CONCLAVE casts the stakeholder populations β fishermen, the developer, the local government, the regulator β fills each with individually-varied simulated agents, and steps them forward month by month, grounded in real retrieved news rather than invention.
-
It argues. Five AI analysts with fixed, conflicting mandates read what the simulation produced and fight over what it means. One is structurally required to attack the emerging consensus. Only then does the Chair synthesise a verdict β and the verdict is obliged to carry the dissent with it.
The output is not prose. It's a dashboard: the calibrated verdict, the sentiment timeline of every population across the run, a tracked list of predictions being scored against reality, and a recurring morning briefing on what has changed.
Scenario Verdict
ββββββββ βββββββ
β β²
βΌ β
ββββββββββββββ ββββββββββββββ ββββββββββββββ βββββββββββββββββββββββββ
β GROUNDING ββββΆβ CASTING ββββΆβ THE FIELD ββββΆβ THE COUNCIL β
β β β β β β β β
β GNews + β β stakeholderβ β tick loop β β 8 turns, 5 personas, β
β yfinance + β β populationsβ β 1..N, β β mandatory contrarian β
β your docs β β + agents β β sentiment β β protocol, calibrated β
β β β + entity β β per group β β synthesis β
β β β graph β β per tick β β β
ββββββββββββββ ββββββββββββββ ββββββββββββββ βββββββββββββββββββββββββ
seed cast runs debate + verdict
β β β β
βββββββββββββββββββ΄ββββββββββββββββ΄βββββββββββββββββββββββββ
every stage streams live over SSE
The Field is where the scenario actually plays out. It is not one LLM narrating a story; it is a population model with a time axis.
Grounding. Before anything is simulated, CONCLAVE gathers real seed material β GNews articles, market data via yfinance, and any documents you supply. Retrieval is deliberately strict: query terms and relevance terms are ranked oppositely, because dates identify a scenario precisely but are useless for searching (headlines are written about the subject, not the deadline). An article must match a relevance anchor and a topical term to be admitted. Without this, the simulation drifts into plausible fiction β which is the failure mode the whole architecture exists to prevent.
Casting. CONCLAVE proposes the stakeholder populations for the scenario and you approve or edit them before anything runs. Each approved group is filled with a roster of individually-varied agents β capped at 12 per group and 40 overall, because the point is population dynamics, not a cast of thousands. Casting also extracts an entity graph (people, organisations, places, resources, populations) with weighted relations, which the UI renders as a navigable map of the scenario.
The tick loop. A run advances in discrete ticks β months by default, eight of them β with one LLM call per cast group per tick. The critical semantic:
Groups within a tick are independent. Every cast group in tick T is prompted with ticks 1..T-1 and nothing else. Their calls are issued concurrently, and no group can see what another produced in the same tick.
This matters more than it sounds. The loop originally ran groups sequentially and appended each narrative to shared history immediately β so group N read groups 1..N-1's same-tick output while group 1 read none, and "group N" meant whatever order Postgres happened to return rows in. Two runs of the same scenario could diverge because of a row ordering nobody chose. A tick is one unit of time; everything inside it happens at once.
Sentiment and forking. Every group emits a sentiment reading each tick, charted as a timeline across the run. Any run can be forked at any tick β branch the timeline, change an assumption, and compare the two futures side by side.
Failing loudly. A run aborts after six consecutive LLM failures. A streak that
long means an exhausted provider quota rather than a transient blip, and continuing
would silently write neutral 0.0 placeholders over every remaining group and tick.
Runs that completed on degraded output are flagged as such rather than presented as
clean.
Five personas, each with a fixed system prompt, a fixed role, and a fixed colour used consistently across the entire interface β so you learn to recognise who is speaking without reading the name.
| Persona | Role | Colour | Mandate |
|---|---|---|---|
| VECTOR | The Quant | #56c8e8 cyan |
Every claim anchored to a base rate, a number, or a comparable historical case. Challenges anything unquantified. |
| BACKCHANNEL | The Insider | #6fce8f green |
Follows incentives, positioning, and the signals official data misses β personnel moves, procurement, financial exposure. |
| LONGVIEW | The Strategist | #e8b556 amber |
Second- and third-order effects. Not "will X happen" but "if X happens, then what β and what after that?" |
| CASSANDRA | The Contrarian | #ef6461 crimson |
Structurally guaranteed dissent. When the room agrees, finds the strongest case against. Disagreement is specific and evidence-based, never sport. |
| THE CHAIR | The Synthesizer | #d8d4ca bone |
Directs the session, invokes the contrarian protocol, and writes the final calibrated verdict without flattening disagreement. |
The turn order is fixed, and the contrarian protocol is mandatory:
1. CHAIR opens, frames the question, calls VECTOR
2. VECTOR base rates and precedent
3. BACKCHANNEL incentives and insider signals
4. LONGVIEW second-order effects
5. CHAIR invokes the contrarian protocol
6. CASSANDRA the strongest case the room is wrong β mandatory, not optional
7. VECTOR response to the challenge
8. CHAIR synthesis + verdict
The Chair cannot reach a verdict without first asking Cassandra to attack it. The resulting verdict is structured, not prose: probability, confidence, summary, strongest dissent (attributed to the persona who raised it), and the opinion split across the panel. A CONCLAVE verdict that hides its own strongest objection is a malformed verdict.
Debate turns stream into the UI token by token as they are generated.
- Predictions β verdicts become tracked predictions with resolution dates, scored against what actually happened, so the system's calibration is visible over time rather than asserted.
- Morning briefings β a recurring digest of what moved: new signals on your open scenarios, predictions approaching resolution, sentiment shifts worth knowing about.
- Discover β browse public councils and their verdicts.
A hard constraint of the project: CONCLAVE must always run on free models. No
metered models, no "just add $10 of credits." Code defaults stay free-tier so a
missing .env fails loudly rather than silently billing.
That constraint is not a single API key β it is a routing layer.
Lanes. Calls are classified by what they need, and each lane has its own model and its own provider chain:
| Lane | Used for | Needs |
|---|---|---|
json |
casting, roster generation, structured extraction | strict schema adherence, high volume |
prose |
tick narratives, debate turns | fluency and length |
verdict |
final synthesis | the strongest reasoning available |
Provider chain. google β groq β mistral β openrouter, re-rankable per lane:
- Google AI Studio (primary) β roughly 1500 requests/day and ~1s latency on flash-lite. The free quota is enforced per (project, model), so each lane names a rotation of sibling models: N models give N Γ 15 RPM against the same quota.
- Groq (overflow) β generous daily budget, tighter per-minute ceiling.
- Mistral La Plateforme (overflow) β free "Experiment" tier, ~1B tokens/month, no card required. Ranked ahead of OpenRouter because a monthly token allowance beats 50 requests per day. Leave pay-as-you-go disabled; that enforces the zero-spend rule at the provider itself.
- OpenRouter (last resort) β hard cap of 50 free requests/day.
Hard-won details baked in: models are health-checked before being added to a rotation (one Gemini preview served a flat 503 and silently slowed an entire run; two others 404 on the OpenAI-compatible endpoint). Rate limits are paced per model, not per provider. And 429 handling reads the metric name rather than guessing from cooldown length β the old "a cooldown over 30s means a daily cap" heuristic was wrong every single time it fired, because per-minute 429s routinely ask for a ~59s wait.
| Layer | Technology |
|---|---|
| Frontend | React 19 Β· Vite Β· TypeScript Β· Tailwind CSS 4 Β· TanStack Query Β· Recharts |
| Backend | Python 3.12 Β· FastAPI Β· SQLAlchemy 2 (async) Β· Alembic |
| Database | PostgreSQL 16 β single system of record |
| Cache / Pub-Sub | Redis 7 β drives live updates |
| Realtime | Redis pub/sub β Server-Sent Events β browser |
| Auth | JWT (HS256), bcrypt password hashing |
| LLM | Multi-provider free-tier routing via the OpenAI-compatible SDK |
| Grounding | GNews Β· yfinance Β· user-supplied documents |
| Delivery | Docker Compose Β· nginx Β· PWA |
Long-running work (casting, the tick loop, debates) executes as background tasks that publish progress to Redis channels; the browser subscribes over SSE and the UI fills in live. Nothing blocks an HTTP request waiting on an LLM.
The interface is a bento grid β self-contained cards of varying size, each holding one thing β rendered in a dark terminal register. Three card materials carry meaning, and the meaning is the point:
| Material | Look | Reserved for |
|---|---|---|
| Void | dark, low-emphasis | the default β lists, tables, anything data-dense |
| Signal | translucent colour tint | headline statistics only β the "this number matters" material |
| Dossier | warm parchment #e8e0cd |
only a completed Verdict or a Morning Briefing |
Dossier is the interface's one moment of physical weight β the council just handed you a document. Its impact depends entirely on staying rare; using it anywhere else would make it stop meaning anything.
Beyond that: one accent, signal lime #c8f04a, used strictly for action,
selection, and live state β never decoration. Five fixed persona colours. Tabular
numerals everywhere, so columns of figures align instead of drifting. Navigation is
a slim icon rail on wide screens and a thumb-reachable bottom row on phones. Motion
stays in the 120β260ms band, because users are in a task, not watching choreography.
Requirements: Docker and Docker Compose. One free Google AI Studio API key.
git clone git@github.com:JimmYCHUU/CONCLAVE.git
cd CONCLAVE
cp .env.example .env
# Fill in GOOGLE_API_KEY (required) β https://aistudio.google.com/apikey
# Optional: GROQ_API_KEY, MISTRAL_API_KEY, OPENROUTER_API_KEY for overflow capacity
# Optional: GNEWS_API_KEY for real news grounding
# Required for anything non-local: set a real JWT_SECRET
docker compose up -d --buildOpen http://localhost:3000 β sign up, create a scenario, approve the cast, watch The Field run live, then convene the Council.
Database migrations run automatically on backend startup. API docs are at http://localhost:8000/docs.
All configuration is environment variables; see .env.example for the annotated set.
| Variable | Required | Notes |
|---|---|---|
GOOGLE_API_KEY |
yes | Primary LLM provider. Free at Google AI Studio. |
JWT_SECRET |
yes | No usable default β set a real secret before any deployment. |
GROQ_API_KEY |
no | Overflow capacity. |
MISTRAL_API_KEY |
no | Overflow capacity. Keep pay-as-you-go disabled. |
OPENROUTER_API_KEY |
no | Last-resort overflow, 50 req/day. |
GNEWS_API_KEY |
no | Real news grounding; without it, NEWS_GROUNDING should stay off. |
LLM_CHAIN_{JSON,PROSE,VERDICT} |
no | Comma-separated provider order per lane β re-rank or disable providers. |
{PROVIDER}_MODEL_{JSON,PROSE,VERDICT} |
no | Comma-separated model rotations per lane. |
{PROVIDER}_RPM |
no | Per-model rate limit. |
DATABASE_URL / REDIS_URL |
no | Defaulted by Docker Compose. |
Never commit a real key.
.envis gitignored and excluded from Docker images via.dockerignore, so keys are never baked into a build.
The E2E suite drives the real stack in a real browser with the backend in
fake-LLM mode (LLM_FAKE=1) β deterministic canned responses, no API keys, no
network calls, no quota burned.
pnpm install
npx playwright install chromium
pnpm e2e:stack # docker compose with the test override (LLM_FAKE=1)
pnpm test:e2e # 7 testsCovering: authentication, the full scenario β cast β field β debate β verdict journey, run forking, mid-run injection, and discover.
Return to real mode with docker compose up -d.
pnpm dev # frontend on :3000, expects backend on :8000
cd backend && uvicorn app.main:app --reload # backend, needs postgres + redisThe fastest hybrid loop is docker compose up -d db redis for the infrastructure,
with the frontend and backend running natively for hot reload.
CONCLAVE/
βββ backend/
β βββ app/
β β βββ routers/ auth Β· councils Β· runs Β· debates Β· predictions Β· dashboard
β β βββ services/ casting Β· simulation Β· council_debate Β· news Β· graphing
β β βββ llm/ client (routing, lanes, rate limits) Β· personas Β· prompts Β· fake
β β βββ models/ user Β· council Β· field Β· debate Β· prediction Β· briefing
β β βββ config.py provider chains, model rotations, RPM ceilings
β β βββ sse.py Redis pub/sub β Server-Sent Events
β β βββ security.py JWT + password hashing
β βββ migrations/ Alembic
βββ src/
β βββ pages/ auth Β· dashboard Β· new-scenario Β· field Β· council Β· discover
β βββ components/ ui Β· layout Β· charts Β· graph
β βββ styles/globals.css the Bento Terminal design tokens
β βββ lib/ api client Β· personas Β· types
βββ e2e/ Playwright suite
βββ docs/ foundations Β· SRS Β· SDD Β· TDD plan Β· TODO
| Document | What it covers |
|---|---|
docs/00_PROJECT_FOUNDATIONS.md |
The complete concept guide β every technology and idea CONCLAVE depends on, explained from zero assumed knowledge. Read this first. |
docs/01_SRS.md |
Software requirements specification |
docs/02_SDD.md |
System design β schema, endpoints, algorithms |
docs/03_TDD_PLAN.md |
Test plan |
docs/TODO.md |
Outstanding work, ordered by value |
Working end to end: grounding, casting, the tick loop with forking and injection, the full council debate with streaming, calibrated verdicts, prediction tracking, morning briefings, and the free-tier routing layer with degraded-run detection. Seven E2E tests green; a real-model run completes in roughly 85 seconds with zero failed calls.
Open items are tracked in docs/TODO.md β chiefly nailing down the remaining daily
quota ceilings, which cost a model its entire day's allowance to measure.