Roundtable

Open source · Python · runs on the logins you already have

Four AIs walk into a repo. One of them is paid to disagree.

Roundtable seats Claude, Codex, Grok, Gemini and your local models at one table and makes them do what a good team does: propose, attack the proposal, revise, build, run the tests, and report the argument honestly. No voting. No group hug.

No API keys needed Every call logged to SQLite Nothing merges without you MIT
Claude
Grok
Codex
Qwen (local)

Illustration. The real transcript is further down.

"I have reviewed my own work and found it excellent."EVERY MODEL, WHEN ASKED TO CHECK ITSELF

Asking one model to grade its own homework is not a review.

Sending the same prompt to five models and taking a vote is not a team either. It is a focus group. Roundtable gives each model a job, a limited view, and a reason to be difficult. The critic never learns who wrote the plan. The validator never sees the plan's reasoning, only the code and the test output. A model claiming something works is filed under "claims", not "evidence".

How it works

A meeting with an agenda, a bouncer, and a test suite.

A Python orchestrator owns the workflow. The models never talk to each other directly; everything goes through it, as validated JSON, into a SQLite log you can read afterwards.

Propose

The proposer turns your objective into a plan, assumptions, risks, and a checklist of acceptance criteria. If the job is too big for one sitting it says so instead of pretending.

Attack

The critic looks for wrong assumptions, missing edge cases, untestable criteria and unearned confidence. It is not allowed to accept with an open blocker, or to accept without saying what it checked.

Revise

The proposer answers every problem: fixed, rejected with a reason, or deferred. Ignoring one is not an option. Repeat until accepted or the round limit hits.

Lock

The acceptance criteria are hashed and frozen before any code exists. Nobody gets to move the goalposts after seeing the score.

Build

The engineer works inside a fresh git worktree on its own branch, with its own tools, the way you would run it by hand. The orchestrator commits the diff.

Test

The orchestrator runs your test command itself, with API keys stripped and a timeout. Exit code and output are recorded verbatim. This is the only source of truth.

Verify

A different model checks each locked criterion against the files and the real test output. "The code looks right" is rejected as evidence.

Report

What was built, what the tests said, every disagreement with both sides, the remaining risks, and the single most useful next experiment.

Budgets for calls, tokens, dollars, rounds and minutes are checked before every call. When one trips, the run stops and you still get the report. A halted run that explains itself beats a finished run that lies.

From an actual run

Four rounds of a team being useful to each other.

Objective: a small max-drawdown function with tests. Condensed from the audit log of run 53f500 on 2026-09-25. The names are the real models; the bugs were real too.

CLAUDE · proposeSingle-pass running-peak scan, drawdown = (peak − v) / peak. Raise ValueError on any value ≤ 0. Ten tests. Confidence 0.90.
GROK · critique[major] After a positive peak, equity 0 is a defined drawdown of 1.0 and −5 is 1.05. The guard treats a defined result as an error. [minor] AC7 gates on exactly "10 passed", which freezes your policy into the suite. Verdict: REVISE.
CLAUDE · reviseFixed: guard moved from the value to the peak. Fixed: AC7 now gates on exit 0, not a count.
GROK · critique[blocker] "if peak ≤ 0: raise" contradicts requirement 1: [0, 100] and [−10, −5] are monotonic and must return 0.0. It also aborts on a leading zero before a later peak; [0, 100, 50] should be 0.5. Verdict: REVISE.
CLAUDE · reviseFixed: removed the raise entirely. Peak starts at 0.0 and the ratio runs only once peak > 0. Added [0, 100, 50] → 0.5 and [−10, 100, 25] → 0.75 as tests.
GROK · critique[major] AC4 claims the fixtures lock a running high-water mark, but every one of them also passes an implementation that resets the peak on any uptick. Your tests cannot tell right from wrong. Verdict: REVISE.
CLAUDE · reviseFixed: added partial-rebound fixtures expecting 0.7 and 0.3, where a reset-on-uptick version returns 2/3 and 2/9. Confidence 0.94.
GROK · critiqueTraced the loop on twelve series including the rebound cases. Verdict: ACCEPT. Two notes for the record: [100, 10, −10] would distinguish high-water mark (1.1) from pair-max (2.0) and nobody tests it; "never raises" is only true for numbers.
ORCHESTRATORAcceptance criteria AC1–AC7 locked. sha256:31b8fbd2…
CLAUDE · buildWrote dd.py and test_dd.py in worktree roundtable/run_53f500. 14 test functions, 20 cases. Ran pytest locally. Commit 0e8bbe5.
ORCHESTRATOR · testpython -m pytest -q → exit 0, 20 passed, 0 failed, 0.23s
QWEN · verifyAC1 ✓ test_empty_returns_zero. AC3 ✓ test_ends_at_zero_trough_total_loss. AC4 ✓ test_partial_rebound_keeps_high_water_mark. AC6 ✓ test_negative_trough_exceeds_one. AC7 ✓ 20 passed. Verdict: ACCEPT, 7 of 7.
CLAUDE · reportACCEPT. Two disagreements kept on file: a series that never goes positive returns 0.0 (the critic says that hides a real loss), and the [100, 10, −10] convention rests on a docstring, not a test. Recommended next experiment: add that one test.

The point is the third Grok message. The tests all passed, and Grok noticed they would also have passed for a wrong implementation. That is the entire job of a critic, and it took twelve calls and twenty-five minutes on subscriptions, no API keys.

The seats

Every chair has a job. Nobody has all the tools.

Roles are config, not code. Point any seat at any backend: the CLIs you already pay for, a vendor API, or a model running on your own machine.

PROPOSER

Writes the plan

Turns an objective into a claim, an approach, assumptions, risks and a definition of done. First it judges scope; an objective too big for one task gets split, not bluffed.

Sees: objective, requirements. Cannot touch files.

CRITIC

Finds what is wrong

Reads the plan cold, without knowing who wrote it, and hunts for wrong assumptions, logic errors, security holes, overfitting, missing edge cases and criteria that cannot be tested. Padding the list to look thorough is as bad as rubber-stamping.

Sees: the plan. Owns nothing; can only block.

ENGINEER

Builds it

Runs as a real agent inside a disposable git worktree on its own branch, with its own editor and shell, and runs the tests itself before handing back. May not weaken a test to make it pass.

Sees: plan, locked criteria, the repo. Only writable seat.

VALIDATOR

Checks the receipts

Gets the locked criteria, the changed files and the real test output. Not the plan's reasoning, not the critic's opinions. Every criterion needs a citation: a file and line, a test name, a line of output.

Sees: artifact and evidence only.

SYNTHESIZER

Tells you the truth

Writes the report from the records, not from vibes. Keeps every disagreement with both positions. Its ACCEPT requires evidence; a plan-only or halted run is never a success.

Sees: everything, after the fact.

ORCHESTRATOR

Runs the meeting

Plain Python. Picks the next stage from a fixed table, builds each seat's limited view, validates every reply against a schema, enforces budgets, commits diffs and runs tests. It is the only thing in the room that never has an opinion.

Sees: all of it. Is not a model.

House rules

Rules that make the disagreement worth having.

01

A model saying it works is never evidence.

Only an executed test, a cited line, or a quoted output counts. Confidence scores are recorded so you can see who was wrong and how sure they were.

02

The critic is not paid to agree.

An ACCEPT with no problems must list what was checked. An ACCEPT with an open blocker fails schema validation and goes back for repair.

03

Goalposts lock before the ball is kicked.

Acceptance criteria are hashed when the plan is accepted. The same idea protects your trading research from "tune until the backtest looks good".

04

Disagreements are kept, not averaged.

A criticism the proposer rejected or deferred appears in the report with both sides. Consensus is not forced, because forced consensus is how bugs get tenure.

05

Nobody gets everything.

Each seat receives a purpose-built context, never the whole transcript. Calls stay the same size in round three as in round one, and no seat can read another's mind.

06

The meeting has a budget and an end.

Calls, tokens, dollars, rounds, minutes. When one runs out the run stops and writes its report anyway. "Insufficient evidence" is a valid and respected outcome.

07

Nothing merges without you.

The engineer works on a branch in its own worktree. Your checkout is untouched. Read the diff, run it, merge it or delete it.

08

Everything is on the record.

Which model, which prompt, what it saw, what it produced, how many tokens, how long, what changed after. One SQLite file per project; one command to print it all.

Get started

Five minutes, zero API keys.

If you already run claude, codex or grok from a terminal, you already have everything Roundtable needs. It drives those same CLIs with your existing logins.

# get it
git clone https://github.com/bronette/roundtable
cd roundtable && uv sync

# what can run right now?
uv run roundtable providers -c examples/project.yaml

# hold a meeting
uv run roundtable run examples/project.yaml

# read the minutes
uv run roundtable calls <run_id> --full
Point it at your own projectSet project.repo to any directory. Git repos get a worktree on a new branch. Set test_command to whatever proves the work.
Mix and match seatsClaude proposing, Grok critiquing, Codex building, a local qwen validating for free. Or all Claude. Or all local. One line per seat in project.yaml.
Budget honestlyEach CLI call carries the tool's own system prompt, so a run is roughly six to nine calls and ten to twenty minutes, most of it the critic thinking. That is the price of a real review.