-
-
Crier: an open-outcry trading floor where the traders are programs, the money is somebody else's, and the bell is a person.
-
Round 6 of the first-light floor: a chit held for an unrated counterparty, read aloud by a local model that moves none of its figures.
-
The same floor's tape: 339 sealed lines, one per shout, print, hold and processor event, each carrying the seal of the one before it.
-
The audit room for that same floor: 41 postings, debits and credits that tie, and a scrubber back to any line.
-
The front door: change houses per side and the ring re-seats before anything is pressed.
-
/docs, where every threshold is printed from the module that enforces it.
What it is
Crier is a trading floor. Autonomous agent houses take seats around an open-outcry market, shout bids and offers, and a central limit order book prints whatever crosses. Every print becomes a trade that escrows money on a double-entry ledger, books a platform fee, and pays the seller. Every shout, print, hold and release is signed by the house that spoke it and sealed into a hash chain as it happens.
Anything that a risk rule flags - an amount over the auto-settle line, a price far from the board, two strangers settling for the first time, one account taking too much of a session, a house with no reputation - stops on a pile in front of a person. That person decides whether it settles. The platform may match; it may not settle past a human.
The floor speaks, and a model does the speaking
A settlement that stops on the pile is a decision somebody has to make about other people's money, and the card in front of them is written in minor units and rule ids. So the floor has an interpreter: it takes the sealed facts of that one settlement and says them back in plain English, in two or three sentences, on the card, with the name of the channel that wrote it. Three channels, tried in order - any OpenAI-compatible endpoint you point it at, the smallest chat model sitting on a local Ollama server, and a deterministic template built from the same facts. A fresh clone with no credentials and nothing installed speaks through the template; the same code on a laptop with ollama serve running speaks through a model, and says which.
What the model is not given is a number to decide. It is handed the amounts the engine already fixed and asked to talk about them; it sets no price, approves nothing, and its prose is never sealed into the tape. Every reading is then checked against the card it claims to read - the amount has to be quoted, the payer has to come before the payee, and a figure the record does not hold is reason enough to throw the paragraph away - so a model that drifts costs the reader a plainer sentence rather than a wrong one. There is a test that reads a real held settlement through the real route and asserts, before and after, that the session's message count and head hash did not move.
Why it exists
Agent-to-agent payments are the easy half. The hard question is what a platform is allowed to do with the money it is holding while it watches agents negotiate on its behalf. Most answers are either "nothing, humans approve everything" (which does not scale) or "everything, within limits" (which nobody can prove afterwards). Crier takes a position: the platform can be trusted to match and to book, it cannot be trusted to settle silently, and the difference has to be checkable by somebody who was not in the room.
What you can do in thirty seconds
- Open the live URL and press Take the floor - no account, no key, no database.
- Press Ring the bell. Houses shout; the board prints; the tape grows a sealed line per word.
- Watch the human pile on the right. Those are settlements the engine refused to complete alone, each with the rule that caught it. Press have the crier read this chit and the floor says the card back in plain English; then Let it through or Refuse it - both are signed by your own key and land on the tape.
- Open audit and drag the scrubber back to any line. The journal recomputes as of that moment: debits, credits, the trial balance, and whether the chain still verifies.
- Press break the tape on purpose, edit one line by one unit, and watch every seal after it stop verifying. Then press take the tape and audit the same file with
npm run audit:ledgeron your own machine, which has no way to reach this one.
How the money actually moves
A settlement runs through a payment gateway behind an interface with two implementations. The default is a deterministic local processor, which is what the live demo and the test suite use so that anybody can run the whole thing with zero credentials. The other is the PayPal REST API: an Orders v2 create and capture on the buyer side, a capture refund when a settlement is reversed, a Payouts batch to the seller, and pushed webhook events - verified through PayPal's own signature-verification call and attributed to the trade they belong to before any of them reaches the books. The local processor emits the same CHECKOUT.ORDER.APPROVED and PAYMENT.CAPTURE.COMPLETED names, so that handling is exercised by the tests with or without credentials. Point .env at sandbox credentials and the same floor settles real, fake money; the checkout slips and the return/cancel routes are already in the app.
How a word on the floor is proved
PROTOCOL.md specifies it: canonical JSON, a body hash per message, an Ed25519 signature over the previous seal and that body hash, and a seal hash that chains forward. Keys are derived from one master seed per session, so a recording carries its own public keyring and can be verified by a stranger holding nothing but the file. Verification never throws - it reports where it stopped trusting the record, which is the difference between an audit and a stack trace.
How a serverless floor stays a floor
Rooms live in one process's memory, and a public URL gets spread over many processes. So every request quotes the room's pointer - the seed and the shape the id is hashed from - and a machine that never saw the floor rebuilds it and rings the bell until it stands where the caller is standing. The replay is exact: the same 339 seals, the same head hash, on a box that was not running when they were spoken. What replay cannot bring back is a decision signed at the desk afterwards, so the answer says when it rebuilt and the screen says so out loud. No database, and no claim that is not checkable.
What it is not
Not durable - the tape is the record, and you keep it by taking it. Not an operator console - one floor's pile, no roles; set OPERATOR_TOKEN and the actions that speak for the platform demand the key. Not a price forecast - the houses have strategies, not alpha.
Stack and checks
Next.js 16 App Router, React 19, TypeScript strict, Tailwind v4, ag-grid for the journal, node:crypto for Ed25519 and SHA-256, PayPal REST (Orders, Payouts, webhooks). 184 tests across 12 files, a language audit that walks every shipped file, a ledger audit that re-verifies an exported tape from scratch, a Playwright pass that walks the desk in a browser - and runs against the hosted URL as well as a local one - and one command, npm run verify, that runs the lot and builds for production.
How we built it
The engine was written before the screens, and written as a pure function of a seed, because a market you cannot reproduce is a market you cannot audit. That decision carried everything after it: the tape became the product rather than a log, the audit room became a first-class screen, and the deployment problem (serverless processes that must not remember anything) turned out to be solvable with the same seed instead of a database.
The risk rules were written the other way round - starting from "what would make me refuse to let this settle without looking" - which is why the pile catches a first-time pairing and a one-sided session as readily as a big number. Every hold names the rule that caught it in the language of the floor, so a person refusing a settlement is signing a reason, not a click.
Two things were treated as non-negotiable and are worth naming: nothing shipped may be unverifiable (there is a test that forges a tape and expects the audit to catch it, and a button on the live site that does the same demonstration in front of you), and the whole surface is English-only, checked by a script rather than by memory.
The interpreter was designed backwards from the same rule. A model that writes a sentence about a settlement is a model standing between a person and that person's money, so it was given a job where its weakness does not matter and its strength does: it may be verbose, it may reorder a clause, it may be plainly wrong about style - and it may not be the thing that says how much. The arithmetic stays in the engine, the reading is checked against the card, and the card is where the reader looks anyway. That is also why the same prose is available with no model at all: the discipline is the design, not the channel.
The model, and what it is allowed to do
The floor's interpreter reads a stopped settlement to the person who has to decide on it. It is OpenAI-compatible, so it will use any hosted endpoint named by AI_BASE_URL / AI_API_KEY / AI_MODEL; with none set it asks a local Ollama server and takes the smallest completion-capable model installed - the readings on this build were spoken by gemma3:4b on CPU. The model is handed only amounts the engine already fixed, decides nothing, and its prose is never sealed into the signed tape; every reading is checked against the card it claims to read and thrown away if it contradicts it. With no model reachable the same facts are assembled by a deterministic template, so nothing on the floor depends on a vendor being up.
Built with, and how each piece is used
PayPal is the settlement leg: Orders v2 create and capture for the buyer, a capture refund when a settlement is reversed, Payouts for the seller, and pushed webhook events verified through PayPal's signature-verification endpoint against their certificate before a word of them reaches the ledger (CHECKOUT.ORDER.APPROVED and PAYMENT.CAPTURE.COMPLETED are the ones the floor acts on). Both are behind one gateway interface with two bodies - the real API and a deterministic local processor - so the floor is playable with no credentials and settles for real when a sandbox is attached.
AG Grid is the journal in the audit room - the double-entry postings of a session, floorable and sortable, with the trial balance and the chain verdict next to it.
Vercel hosts the live floor. It is the interesting host for this project rather than an easy one: a trading floor is stateful and serverless functions are not, which is what forced the pointer design - every request quotes the room's seed and shape, so any instance can rebuild the room and ring it forward to the round the caller is standing in.
Notes for anybody who runs it first
- The live demo runs on the local processor, so the desk is open and the money is play money inside one floor's ledger. With PayPal sandbox credentials attached, the same screens settle through real Orders, Captures and Payouts - that path was walked on this machine, and a held chit came back with PayPal's own checkout page in its link.
- The hosted floor deliberately holds no credentials: tick settle on the real processor there and it answers that this machine was never given permission, rather than opening orders under somebody else's account. Put the pair in your own environment and the same code does the real thing.
npm install && npm run devneeds no credentials at all.npm run pit -- --seed first-light --ticks 6runs the same floor from a terminal, no browser.- The interpreter on the hosted floor speaks from the template, because the machine answering your browser has no model on it. Run the same code next to an Ollama server, or name an endpoint in
.env, and the identical card is written by a model and says so. Nothing else on the screen changes. - A chit's reading takes a few seconds when a CPU model is writing it and effectively none when the template is; the card says which, and the number on the card is the engine's either way.
Built With
- ag-grid
- ed25519
- ffmpeg
- next.js
- node.js
- ollama
- paypal
- playwright
- react
- tailwind-css
- typescript
- vercel
- vitest
Log in or sign up for Devpost to join the conversation.