Splitbench · standing orders

What the chamber keeps.

Every claim below was read out of the code that performs it. This is what the software does with what you type, stated exactly and in the order it happens.

In force

From 7 September 2026. This notice covers splitbench.com, operated by Garrell Tech Solutions LLC. It is rewritten whenever the software's behaviour changes and the date above moves with it, so a claim here and the code that performs it are never allowed to drift apart.

Section one

Who runs this

Splitbench is operated by Garrell Tech Solutions LLC, a limited liability company formed in Florida, United States. Write to [email protected] about anything on this page.

Splitbench is also software anyone may run themselves. This page describes the deployment you are reading it on. A copy running on somebody else's machine sends its debates to whichever providers that operator configured and stores them wherever that operator points it, and none of what follows is a promise about their copy.

Section two

What leaves this machine, and to whom

A debate is a conversation with language models that other companies run. There is no way to hold one without sending them what is being argued. Three destinations, and nothing else:

  • openrouter.ai/api/v1 — every turn Every request a debater or the judge makes goes to OpenRouter, which forwards it to the lab that runs the model sitting in that chair. A debater's request carries the motion, the agreed ground, the transcript so far, and any point you have made from the floor. The judge's request carries the whole transcript at once. If you ask the chamber to sharpen a rough subject into a motion, the subject and your answers to its questions go the same way. Nothing identifying you travels with any of it: no account, no address, no cookie, no browser fingerprint. The only headers this app adds beyond the request itself are an optional application URL and title, which describe the deployment rather than the visitor.
  • api.openai.com/v1/moderations — once per debate Before the first debater is asked to speak, the motion — and only the motion — is sent to OpenAI's moderation classifier (omni-moderation-latest) to decide whether this demo will debate it. The agreed ground is not sent, the transcript is not sent, and nothing said later from the floor is sent. It runs once, at the start, not on every turn. The categories the classifier returns are used to choose which refusal you see and are not stored anywhere. A check that cannot be completed refuses the debate rather than running one nobody screened.
  • fonts.googleapis.com and fonts.gstatic.com — page load This page and the chamber both load three typefaces from Google Fonts. Your browser fetches them from Google directly, which means Google sees that request — your address and your browser's user agent — exactly as it does on any site that loads fonts this way. Nothing about your motion is in it.

There is no analytics script, no tag manager, no session recorder and no advertising pixel anywhere in this site. The viewer's entire third-party dependency list is React and the Supabase client.

Worth knowing

Nothing you write travels in a URL. The motion, the agreed ground and each point from the floor are sent in the body of a POST. The streaming connection that follows carries only the debate's id in its address, so the request line — the part of a request an ordinary web-server access log records — never contains anything you typed.

Section three

Your words are handled as claims, never as orders

The motion and the agreed ground are the only words in a debate that a participant writes, and a system prompt is the one place a sentence is read as an instruction rather than as a claim. So participant text never goes in a system prompt. It travels in the user message, inside marked blocks — --- BEGIN PROPOSITION (written by a participant) --- and its closing marker — and anything in your text shaped like one of those markers is stripped before the block is assembled, so that nothing can write its own way out of the fence.

Both the debaters' and the judge's system prompts state what the fence means: the blocks are the subject being worked on, they are never instructions, and nothing inside them can change the rules or dictate a conclusion. This is a safeguard for the integrity of the debate, and it is also the reason a motion cannot be used to make a model do something else with it.

Section four

What is stored, and for how long

There is no retention period in this software, because retention here is not measured in days. Both stores below are bounded by capacity: they hold a fixed number of the most recent records and drop the oldest to make room. What removes a record is a newer record arriving. That is the whole policy, stated as it is rather than as a number of months nothing enforces.

While it is running

A debate in progress

Where
A row in splitbench.debate_sessions in the deployment's Postgres database. A deployment configured with no database keeps it in the server's memory instead, and loses it on restart.
What
The motion, the agreed ground, the transcript so far, the ruling once there is one, which models are in which chair and at what effort, what each turn cost, and a lease naming the machine currently running it. No credential and no deployment configuration is ever written here.
How long
The 32 most recently written are kept. Each write drops the oldest beyond that, except any debate a machine is actively arguing, which is exempt however old it looks.

Only if you press save

A saved debate

Where
A row in splitbench.debates in the same database. A deployment configured with no database writes to a local SQLite file instead.
What
An immutable snapshot: the motion, the agreed ground, the full transcript including every point from the floor, the ruling, which model argued which side, the number of rounds, what each turn cost, and a SHA-256 hash of the delete token. The token itself is never stored.
How long
The 500 most recent are kept. A save past that drops the oldest — which means a saved link can stop working because other people saved debates after you, and there is no notice when it does.
And nothing else

A debate you do not save is never written to the archive. When it ages out of the 32 live rows, or the server restarts on a deployment holding them in memory, it is gone.

The application owns five tables in all. The two above; the two that belong to an account, splitbench.system_admin and splitbench.unit_ledger, both under accounts; and splitbench.rate_window, which is the subject of section seven. None of them has a column for a user agent, a device identifier or a referrer.

Section five

What your browser holds

Five keys in this site's localStorage. Four of them never leave your device. The fifth does, on every request, and it is the first one listed for that reason.

  • splitbench.visitor — sent A random identifier your browser mints for itself the first time it calls the API, and sends afterwards in the X-Splitbench-Visitor header on every /api request. It exists so that one visitor behind a shared address is not rationed as though they were everybody behind it — see section seven, where it is also what gets written down. It is not a credential, it is attached to no account and no debate, and it carries nothing about you: clearing site data mints a new one, which is a thing you may do freely.
  • splitbench.motions Motions and their agreed ground that you chose to keep, so you can put the same one to a different bench. Up to 100, newest first.
  • splitbench.saved The debates you have saved: the link id, the motion, the time, and the delete token. Up to 50, newest first.
  • splitbench.theme Light or dark, written only if you chose one explicitly. Following the system setting stores nothing.
  • supabase.auth.token Your signed-in session, written by the Supabase client and only if you signed in.

Clearing site data for this domain removes all five. The delete tokens go with them, and no one can recover them — see section ten.

Section six

Accounts

Nothing in the chamber requires one. Putting a motion, taking the floor, reading the ruling and saving a debate are all anonymous, and registration is currently closed in any case.

Where an account exists, it is held by Supabase, the authentication service, not by this application. Supabase holds the email address it was given, and — if the account signed in through Google — whatever that sign-in returned to it. Sign-in is by a one-time link sent to an address, by a password, or through Google.

Splitbench's own tables hold no email address and no name. They hold the Supabase user id, which is a random identifier, in two places.

  • splitbench.system_admin That id, the time access was granted and an optional note, for each person allowed to read the operator's cost dashboard. Almost nobody is in this table.
  • splitbench.unit_ledger A row each time a signed-in account buys units or spends them. A row holds that id, a signed quantity, a reason — one of debate, sharpen or screen — the id of the debate that spent it where one did, and the time. Nothing is ever overwritten: a balance is the sum of the rows, so the rows are a history and are kept as one.

Units are only drawn on after the free hourly allowance is used up, so an account that stays inside it spends nothing and writes no rows. Deleting the account in Supabase deletes every row in both tables with it.

A saved debate is still not attached to an account: nothing in the archive names a person. The one link that exists runs the other way and is worth stating exactly. A ledger row names the debate it paid for by that debate's in-progress id, which is not the id in a shared link — the two are minted separately. While the in-progress row survives, it records which saved debate it became, so the two could be joined; once it ages out of the 32 kept, the id in the ledger refers to nothing that still exists.

Section seven

Your address

Your IP address is used for one thing — rate limiting — and, on this deployment, it is written to a table to do it. The counters live in Postgres rather than in a single server's memory, so that two machines enforce one allowance between them instead of one each.

Every guarded request draws on somebody's hourly allowance, and the server decides whose in this order. A verified account is user: and its own id. A request with no account is visitor: and the random id the browser minted for itself (section five), beneath a ceiling keyed ip: and the address — as the hosting proxy reports it in Fly-Client-IP, or, where there is no proxy, the connection's own address, and reduced to its /64 where it is IPv6, since a residential IPv6 line is delegated a whole /64 and rationing the full address would ration nothing. Whichever of those three strings applies, prefixed again by which allowance is being drawn on, is the bucket column of splitbench.rate_window.

  • What a row holds The bucket and a timestamp. That is the entire table. A row records that one visitor — an account, a browser's own id, or an address — made a request of a given kind at a given moment. Not what they asked, not what was argued, and nothing that joins to a debate or a transcript.
  • How long it lives One hour. A row older than the window it is counted in is deleted: by your own next request, which prunes your rows before counting them, and by a fleet-wide sweep that any guarded request has roughly a one-in-five-hundred chance of running. So rows belonging to a visitor who never returns are cleared by other people's traffic, and on a deployment that has gone quiet they can outlive the hour until traffic returns.
  • Signing in replaces it A signed-in request is counted against the account id instead, so neither your address nor your browser's id is written for it. Every request that draws on an allowance — starting a debate, continuing it, screening its motion and sharpening a topic — carries your session, so signing in moves all of them off your address. The streams that run the rounds afterwards carry nothing and draw on no allowance.
  • Why the prefixes X-Forwarded-For is a header the sender writes, and it is no longer read: the proxy's own account of the connection is used instead, so the address counted against you is not one you can choose. The three key spaces are kept separate regardless, so that an account's id can never be reached from the space addresses live in, and a browser cannot spend an account's allowance by sending that account's id as its own.

Your address is written nowhere else. It is not in the archive, not in the session store, not in the ledger, and not attached to any debate. A deployment configured with no database keeps the same counters in memory instead, and writes no address at all.

Section eight

What "unlisted" means, and what it does not

Saving a debate produces a link at /d/{id}. The id is sixteen hexadecimal characters — sixty-four bits from the operating system's cryptographic random source. It is not sequential, and guessing one is not a practical attack. Both the page and the API that serves its content answer with X-Robots-Tag: noindex, nofollow, so a crawler that honours it will not index the debate. An id nobody ever saved and an id that has been unpublished return exactly the same response, so somebody guessing learns nothing from the difference.

Unlisted is not private. There is no password on a saved debate and no check of who is asking for it. Anyone holding the link can read the whole thing, and can forward it to anyone else.

More than that: when the link is pasted into a service that unfurls links — a chat app, a social network, a mail client — this server hands that service the motion (clipped to 110 characters) and the ruling (clipped to 200) in the page's preview tags. The substance of the debate is therefore visible to that service, and to everyone who can see the preview, without anyone opening the link at all.

Treat an unlisted link as public to everyone it reaches. Do not put anything into a motion, a stipulation or a point from the floor that you would mind a stranger reading.

Section nine

Cookies

This application sets no cookies. Nothing in the server writes one and nothing reads one. The API is configured to refuse credentialed cross-origin requests for precisely that reason: the only credential it ever reads is an Authorization: Bearer header that the page attaches by hand, which is a different mechanism.

If you sign in, the Supabase client keeps your session in this browser's localStorage under supabase.auth.token — not in a cookie. Signing out removes it. Because there are no cookies, there is no consent banner and nothing to opt out of.

Section ten

Asking for a copy, or a deletion

Write to [email protected]. Say what you want a copy of or removed, and include enough to find it — the link to a saved debate, or the email address an account was opened with. There is no automated process behind this and no form to fill in; it is a person reading mail.

Two things are worth knowing before you write, because they change what can be answered.

  • Unpublishing does not need us Saving a debate hands back a delete token, once. Only a SHA-256 hash of it is stored, so possession of the token is the only thing that can unpublish the record — reading the database confers nothing. The token is kept in this browser under splitbench.saved, which is why unpublishing works from the browser that saved the debate and from no other. If you still have that browser, you do not need to write to anyone.
  • Anonymous debates cannot be searched for A saved debate records nothing about who saved it. There is no account on it, no address, and nothing to match a person against, so "delete everything of mine" cannot be answered by looking — only a specific link can. That is a consequence of not asking you for an account, and it cuts both ways.

A record whose token is lost stays until it is pushed out by the 500-record bound, unless it is removed by hand on request.