How you build a desk of your own,and how you know it worked.
The whole manual, public on purpose: read it before you buy. Every command below is the archive’s own, and every transcript is pasted output rather than an illustration.
paper trading only · Node 23.4+ · Windows · every command below is the archive's own
Part one — setup
Three steps, assuming you have never written code.
One thing to install, one file to double-click, one command to see a real round land. Every step says how you know it worked, because the failures here are quiet ones.
Install Node — the only thing you install
Node 24 LTS from nodejs.org. That is the whole list: no database, no Python, no Docker, no account, no API key. The one thing that goes wrong here is that you already have Node and it is too old — run
node -vfirst. The database isnode:sqlite, which Node did not have before 23.4, so on anything older the health check refuses with a line naming the version it found. Installing 24 replaces it, but close every terminal you already had open, or they keep answering with the old version.node -vYou know it worked when:
node -vanswers with 23.4 or newer.Double-click start.bat
Unzip somewhere with no spaces or non-English characters in the path, and double-click the launcher. It is safe to run twice: it installs only what is missing. It narrates itself — first run installs dependencies for a few minutes, then it runs the health check, then it seeds an offline demo board so the screens are not empty, then it opens the board in your browser. Leave that window open; closing it stops the board.
start.batYou know it worked when: A browser on a board of desks with an equity number each and a leaderboard, a few seconds after the launcher's own last line says it is opening the board. That first board is a DEMO and it is labelled in the data, not just on the screen: its prices are synthetic, every turn record says a rule engine decided it so no turn can claim a model made a decision it never made, and the board's own /api/meta answers
"demoMode": true. Wiping it is one documented action — stop the board and delete the database files under data/.Your first real round, on real prices
Delete the demo database first, so demo turns and real turns never mix. Open a second terminal in the folder you unzipped into and ask for one round now — this command ignores the four-hour clock. The board names itself on the first line, then prints a line per desk as each one decides. A desk you have no key for records a turn naming the variable it wanted and the round carries on without it;
node scripts/keyless.mjswrites you a copy of the board with every one of those switched off, if you would rather not see them at all.pnpm runner --onceYou know it worked when: The terminal's last line reads
done (--once), and your round is on the board: the standings screen carries a row per desk with an equity figure beside it, and opening one shows the turn it has just taken — the order it placed, or its reason for standing aside, in its own words.
The board the runner loads when you have set nothing needs no key to reach a round: its prices come from a keyless feed and its rule engines decide on their own. A desk on it that wants a credential you have not given it records a turn naming the variable it wanted and sits that round out — not a fault, and not something that stops the round; the desks that can decide still do. If you would rather not watch a desk sit out at all, node scripts/keyless.mjs reads the board you are using and writes a copy of it beside your database with every desk that needs a credential switched off. Your own config is never touched, and undoing it is deleting the file.
Two blocks of pasted output, and nothing in them is retouched.
Both failures in setup are silent ones — a health check that printed nothing, and a first round you cannot tell from a board that is merely waiting. So here are the lines.
The health check
Nothing re-wrapped · swipe →✓ Node.js PASS v23.10.0 ✓ node:sqlite PASS built-in ✓ Config PASS "The Watch" — 22 teams, 3 symbols, exchange=yahoo … (16 desk rows dropped here — the summary line below counts every one) ✓ Team gpt-gold PASS local agent responds: codex-cli 0.144.1 ! Team gemini-gold WARN no key in GOOGLE_GENERATIVE_AI_API_KEY — team will sit out ✓ Team qwen-local PASS ollama model qwen2.5:7b present ! Team thrust-btc-1 WARN agent CLI not responding (claude) — team will sit out ✓ Market data PASS yahoo: GC=F last=4491.2 ✓ Database PASS …dataarena.db writable (fresh) Ready. 16 warning(s) — those teams simply sit out.
Ready. is the whole success condition — with a count of warnings after it. A warning is not a failure: a desk with no credential sits the round out and the others trade on. Older than Node 23.4 and the first row fails instead, in the archive’s own words — ✗ Node.js FAIL v… — need ≥ 23.4 for node:sqlite. Sixteen desk rows are dropped above; the ellipsis says where, and every row shown is verbatim.
One round, on a machine with no key of any kind
Nothing re-wrapped · swipe →[2026-08-12T14:26:05.849Z] arena "The Watch" — 4 teams, 3 symbols, exchange=yahoo [2026-08-12T14:26:05.849Z] sitting out this season (config "enabled": false): claude-gold, strat-donchian, … (18 desks) [2026-08-12T14:26:05.850Z] turn every 240m, monitor every 120s, db=…dataarena.db [2026-08-12T14:26:06.905Z] turn evolab-b1 → ok (1.1s) — held [2026-08-12T14:26:07.692Z] turn micro-b1 → ok (0.0s) — held [2026-08-12T14:26:07.697Z] turn hold-b1 → ok (0.0s) — held [2026-08-12T14:26:07.705Z] turn momentum-b1 → ok (0.0s) — opened GC=F long $1000x3; opened SI=F long $1000x3 [2026-08-12T14:26:07.727Z] reading room: gathering the round and asking the free gate whether anything happened… [2026-08-12T14:26:09.100Z] reading room: nothing to write — no writer configured [2026-08-12T14:26:09.114Z] done (--once)
4 desks ship switched on, so a first round is that many turn lines: one of them acted, and from unzip to done (--once) took about four seconds. A desk that stood down says held; one that acted names the symbol, the side and the size it opened. Two shortenings are marked where they happen — the absolute path in db=, and the sitting-out roster. If you never reach done (--once), the round did not finish.
Part two — building your team
Two ways to put a desk on the board, and only one of them needs a screen.
A desk is an object in one JSON file. The board can write that object for you, or you write it yourself — or your own coding agent writes it while you read what it changed. Either way it joins the next round on the same clock, under the same guard, ranked beside the desks that ship.
From the board — the New Desk wizard
The board builds the desk, writes it into your configuration and restarts the runner so it joins the next round. Two honest constraints. It restarts the runner through PowerShell, so this flow is Windows-only until that changes; on another host it stops before touching your config and says which host limit it hit, because a changed roster the runner never picked up would be worse than a refusal. And an agent desk copies its command from an agent desk already in your configuration — the binary, the grants, the flags — so if your config has no working agent desk yet, the wizard refuses to invent one rather than writing a desk that cannot run. Rule desks have no such requirement and can always be created.
By hand — edit the config, restart the runner
Add the desk to the configuration file, save it, and restart the runner. That is the whole of it. The schedule lives in the database rather than in the process, so restarting does not fire an extra round; there is a separate flag for when you want one now.
The press is not the finish line and the board does not pretend otherwise: it waits for the runner’s own heartbeat, and if none arrives within 180 seconds it puts the old configuration back and tells you so. Nothing is left half-applied.
Which host each of these needs
The overview says one word — Windows — because this block is here. Four rows, because the honest answer has parts and one is a line of code rather than a policy.
The New Desk wizard — Windows
Creating or retiring a desk from the board restarts the runner through PowerShell, so that flow is Windows-only. On another host it refuses before it touches your configuration and names the limit it hit: a changed roster the runner never picked up is worse than a refusal.
The wizard's second limit
An agent desk copies its binary, its grants and its flags from a Claude CLI desk already in your configuration. With none there the wizard refuses rather than write a desk that cannot run, so your first agent desk is one you write yourself, from the recipe below. Rule desks can always be created.
The runner — one Windows call a round
At the end of every round the runner spawns cmd.exe to refresh the supervisor's brief. It is fire-and-forget, and the guard around it cannot catch a spawn that never starts, so on a host without cmd.exe that one line ends the process — after the round itself has been written. The board's Turn now button is bound the same way and fails more politely: it looks for tsx.cmd, does not find it, and answers with an error.
Everything else — untested, which is not unsupported
Windows is not confined to those three calls. Process control here is Windows machinery — PowerShell restarts the runner, taskkill ends a hung CLI, .cmd shims run through a shell — in the breed and turn-now routes, in the driver every model desk runs through, and in the maintenance scripts. Some check the host first and step aside; some do not. Nobody has run any of it on macOS or Linux, so nothing about those hosts is claimed here.
Two of them cost nothing, forever.
A desk's kind decides what drives it and what it bills. All four live on the same board under the same guard, ranked side by side — and a board built only from the first two still trades every round.
A rule desk
A fixed rule engine that ships in the source. No model call is ever made, so it needs no key and costs nothing. A board made only of these is a legitimate board and still trades every round — they are the control group the agent desks are measured against.
no bill
A local-model desk
A model running on your own machine through Ollama. No API bill; your hardware and your power instead.
no bill
A hosted-model desk
One completion per desk per round through your own provider key — OpenAI, Anthropic, Google, or any OpenAI-compatible endpoint. One environment variable per provider, read but never printed.
your provider's per-token price
An agent-CLI desk
An agent already installed on your machine — Claude Code, or another CLI — handed the whole round instead of a single completion, with a folder it reads and writes across rounds. This is the kind the example board is mostly built from.
whatever that session's account is charged
Read the ladder per desk: ten agent desks are ten of those bills, every round, for as long as the board is on. The figures come from this board’s own token telemetry — $0.199 a decision on Sonnet-class and $0.893 on Opus-class — and you pay your model provider, never us. 5 of the example board’s 22 desks never make a paid call.
5 strategies on the menu, and two ways past it.
The create screen offers 5 named strategies, all on gold, and locks the desk you build to the ones you pick. Each is a registered lane carrying either a citation to a real tester run or an honest line saying it has none. That menu is short on purpose, and it is not the boundary of what you can run.
Write the desk's own mandate, by hand
A desk carries a free-text persona of its own and is not limited to the menu at all: describe the style you want and the desk trades it. What you give up is the composed mandate's one protection — a mandate the product wrote cannot name a strategy the engine does not have, and one you wrote can.
Register the lane, and be held to the registry's honesty rule
A lane is a named strategy a desk can be locked to: a row in the lane registry, then the id in that desk's lanes array. There is no other way in — the config parser rejects any lane id the registry does not hold, so a fabricated strategy cannot be configured by accident.
Why the second one is gated. Every lane in the registry must carry a stat line that either cites a real tester run or is byte-for-byte one of the honest no-record forms — and the suite plants a confident-sounding invented figure to prove a number in between passes neither arm. So a lane you have not measured says it has no record.
The lanes and the risk cap are instructions inside the desk’s mandate rather than fences in the engine: the engine does not check which lane an order came from, and the desk is judged, in writing, on whether it obeyed. What the engine enforces is the house risk guard, on every order from every desk however it was configured.
The interface is two pipes, and you name the command.
14 of the example board's 22 desks are driven by an agent CLI on the operator's own machine. What such a desk gets that a hosted model does not is a whole session and a folder it keeps between rounds — so it can write itself a playbook, keep a journal, and read both back next time.
What your CLI has to do
cliCommand is a plain array of strings the runner spawns; nothing in it is bound to a vendor. Four things decide whether the binary you name can drive a desk, and a non-zero exit is not one of them — that is forgiven as long as something was printed.
The prompt arrives on standard input
Never in argv, so length and quoting are never your problem. The runner writes the whole round to the child and closes the pipe.
The answer comes back as plain text
It has to contain the decision JSON — bare, after prose, or inside a fenced code block; all three parse, and the last valid object wins. What does not parse is an answer escaped inside the CLI's own JSON event stream: the scanner walks top-level objects and skips the insides of strings.
Leave --output-format json to Claude Code
Those two tokens together switch on the envelope reader, which then looks for Claude Code's result key. Another CLI's envelope falls through to its raw output and lands you back on the rule above.
Your Anthropic key does not travel
ANTHROPIC_API_KEY and every CLAUDE* variable is deleted from the child's environment, so a wrapper script has to read its key from a differently-named one. Other providers' variables pass through untouched.
The four clocks, for planning
Every one of them is a timeout in the shipping source rather than a figure written for this page. They are here so that “my CLI is slow” and “my CLI is broken” are two different questions a buyer can tell apart before paying.
- A plain agent-CLI desk
- 300 seconds
- The same desk with a workspace
- 420 seconds
- The one repair call, when a reply carries no decision
- 180 seconds
- The health check's --version probe
- 15 seconds
Two agents have driven a desk here — Claude Code and Codex, side by side on one board. Nobody has run a third, so nothing about a third is claimed. The Codex desk passes 6 arguments and not one of them is Claude’s.
The shipped health check, on a machine with no Claude installed
Nothing re-wrapped · swipe →✓ Team gpt-gold PASS local agent responds: codex-cli 0.144.1
Putting one behind a desk
Sign the CLI in, by hand, first
Install your agent CLI and sign in until it answers in a terminal of your own. The runner deliberately strips the API-key variables out of the child process, so the CLI authenticates exactly as it does when you run it yourself — with your session, not with a key the board holds.
claude --versionCopy the example board and make it yours
The example configuration is the worked version of this: most of its roster is agent-CLI desks. Copy it to a file of your own and read the block at the top, which names every value you must set.
copy arena.example.config.json my.config.jsonReplace every placeholder path
Each agent desk is fenced to its own workspace folder by absolute path, and the shipped file writes those as a placeholder rather than as one machine's directories. Replace every one of them with the absolute path of the folder you unzipped into. A desk whose fence still says the placeholder is fenced out of its own workspace, and all of its turns fail.
<ARENA_HOME>Delete all but one desk until you have seen a bill
The example board is many agent desks. Each one is its own bill, every round, for as long as the board is on. Start with one, watch what it costs for a day, then add the second. Desks you have no key or no local model for take an enabled-false line and sit out of the walk and the ranking instead of erroring every round.
Check it before you spend anything
Point the health check at your own config — the same value has to go to every process that reads the board — and read the row for your desk.
pnpm run doctorYou know it worked when: The doctor prints a PASS row for that desk reading
local agent responds, with the version it answered. A WARN readingagent CLI not respondingmeans the binary was not found: check it by hand, and if it answers there but not here, put its absolute path in the desk's command.
The desk, as it appears in the config file
An agent-CLI desk, from docs/CUSTOMIZE.md §1b
Nothing re-wrapped · swipe →{ "id": "agent-1", "name": "Agent One", "provider": "cli",
"model": "claude-sonnet-5", "cliWorkspace": true, "cliDecisionSchema": true,
"cliCommand": ["claude", "-p", "--output-format", "json",
"--model", "sonnet", "--permission-mode", "dontAsk",
"--tools", "Read,Write,Edit",
"--allowedTools", "Edit(<ARENA_HOME>/data/workspaces/agent-1/**),Read(<ARENA_HOME>/data/workspaces/agent-1/**)",
"--disallowedTools", "Bash,WebSearch,WebFetch",
"--strict-mcp-config", "--safe-mode"] }- The desk gets a folder it keeps between rounds — the playbook and the journal it writes for itself. Grant its tools against that folder and nothing else; the shipped fences deny shell, web search and web fetch on purpose.
- Every path in the desk's command has to exist on your machine. A configuration copied from another box points at that box's disk, which is exactly why the example ships with placeholders instead of real directories.
- Running an unattended trading desk against a CLI authenticated by a subscription is your call, under that plan's own terms. Read them and decide; this software does not decide for you. The documented, unambiguous path is an API key on a hosted-model desk.
Read that fence for what it is: those flags are Claude Code’s own arguments, in the config because you put them there. This product does not hand a permission fence to a binary it has never seen — the Codex desk carries none. What protects you whichever CLI you named is the next block.
And what the desk is never given
It authenticates as you do, by hand. The runner strips ANTHROPIC_API_KEY and every CLAUDE* variable out of the child, so the CLI signs in exactly the way it does when you run it yourself — with your session, not with a key the board is holding. Nothing here can see or cap what that session is charged, which is why the step above says to watch one bill before adding a second desk.
It does not get to configure its own permissions. Before every turn the harness hard-resets that workspace to its last commit and deletes anything uncommitted, so a half-written file from a killed session can never be traded on. In the same pass it deletes .claude/, CLAUDE.md, .mcp.json, settings.json and settings.local.json — every turn, whether or not anything put them there. Then it stamps a TURN_ID the decision has to echo, and commits the whole folder when the turn ends. None of that inspects your CLI, so all of it holds whichever one you named.
Describe the change to your own coding agent, and check what it wrote.
The archive ships a contract for exactly this. CLAUDE.md is not a style guide and not a pointer to another file: it is 448 lines telling an agent what this codebase is, which parts may be extended, which may never be touched, and the command that proves a change is safe. Six recipes sit in the middle of it.
Each names the files it touches, a prompt you can paste at your agent verbatim, the command that proves it worked, and the way that change goes wrong. Five never leave the configuration file; the sixth edits the source and is the most gated change in the archive.
Add a rule desk
Touches — arena.config.json only.
Proves it worked — pnpm check — the Config row's desk count goes up by one.
Add an agent desk on a local CLI
Touches — arena.config.json, after reading docs/CUSTOMIZE.md §1b.
Proves it worked — pnpm check — that desk's row reads PASS local agent responds, with the version it answered.
Add a hosted-model desk on an API key
Touches — arena.config.json, plus one line in .env the recipe tells the agent to hand you rather than write.
Proves it worked — pnpm check — the row reads key present and names the variable. It reports presence, never a value.
Change a risk cap
Touches — arena.config.json → risk. The guard reads these and holds no copies of its own.
Proves it worked — pnpm vitest run tests/guard.test.ts, then pnpm check. To watch a cap bind, run a round and read the rejections.
Add a market
Touches — arena.config.json → symbols, or quoteSymbols for one that may be read and never ordered.
Proves it worked — pnpm check — Market data passes against the venue and the symbol count goes up.
Add a strategy lane
Touches — src/lib/lanes.ts, then the desk's lanes array, then the registry pin in tests/lanes.test.ts.
Proves it worked — pnpm vitest run tests/lanes.test.ts — and expect it RED before you touch the pin. That test asserts the registry's exact contents on purpose.
One of the six, printed whole
The sentence you would actually paste at your agent verbatim, quoted exactly as the archive writes it. Everything in brackets is yours to replace.
Add a `cli` desk to my arena config, copying the shape of the CLI desk already in `arena.example.config.json`. Id `[agent-1]`, name `[Agent One]`, `cliWorkspace: true`, `cliDecisionSchema: true`. Use `[claude]` as the binary. Replace every `<ARENA_HOME>` with `[C:\arena]` — the absolute path of this folder — so the tool fences point at real directories on this machine. Do not add `researchEveryTurns`. Then run `pnpm check` and show me this desk's row.
Notice what it does besides naming a desk: it points the agent at a worked example in the folder rather than at its own memory, names the placeholder that has to be replaced, says which option NOT to add — that one is a second bill — and closes by asking for a command to be run and its output shown. That last clause is the whole method: you are not trusting the agent’s account of what it did, you are reading the product’s own answer.
The three that actually bite
- A desk id is the database key. Reusing one adopts that desk's whole history and equity; a fresh id starts a fresh simulated account. Neither is wrong, and the config does not tell you which you just did.
<ARENA_HOME>is not a variable. It is a placeholder in the shipped example, and a desk whose tool fence still contains that literal string is fenced out of its own workspace — every one of its turns fails.- A config change reaches the board only when the runner is restarted. The web app re-reads the config when the file changes; the runner reads it once at boot. Edit under a running runner and the screen shows your new roster while the walk still uses the old one.
And the rest of what the config turns
The recipes are the changes with a written procedure. These are the knobs, in the archive’s own order of how often they get turned — all documented, most one field.
- Add or swap a desk, or point one at a different model.
- Put a local agent CLI behind a desk, with its own workspace and its own tool fence.
- Change a desk's persona and the prompt it is sent.
- Tune the risk guard — the caps, the discipline rules, what gets rejected.
- Change fees, slippage, funding and liquidation, or turn each off.
- Add an indicator to the snapshot every desk reads.
- Change the symbols, the cadence and the starting balance.
- Point it at another venue, or at your own broker's prices through the bridge script.
- Reskin the board, or read the SQLite file yourself with any client.
- Publish a read-only public board of your own.
Three commands after any change, and a fourth question.
This is what makes “drive it with your coding agent” a claim rather than a wish: an agent that can run the suite can tell you whether what it just wrote is broken, and so can you. All three are pure Node — no dev server, no browser, no network, no key.
pnpm run doctor
Does this machine still satisfy the board — runtime, config, keys present, market data, database.
For an agent desk it calls your binary with --version: that proves the CLI is installed, not that it can take a turn. And it is advisory — a CLI with no --version flag WARNs and still runs every round.
pnpm test
The suite that ships with the tree, run against your own copy of it — the same suite the product is developed against.
pnpm typecheck
The types, in one pass. The cheapest way to find out that an edit broke something three files away.
A change is finished when all four are true and not before — the archive’s own definition of done:
pnpm typecheckexits 0.pnpm testis green — the same count as before your change or higher, and no test was edited to get there.pnpm checkreports no new FAIL, and its Config row describes the board you meant to build.- You can say in one sentence what would go wrong if your change were reverted — if you cannot, it is not understood well enough to keep.
If a test goes red, the default assumption is that the test is right. Never edit a test to make a change pass — most of the tests in the tree exist because the thing they pin was once broken in front of somebody. And never print, echo or commit the .env file: keys live there, and the health check reports which variable is missing, never what is in it.
Five parts of this source an agent must never touch.
Most of the tree is yours and safe to rewrite; the recipes above are the proof. These five are not. Each is load-bearing for something the product states in writing, and editing one does not customise the product — it replaces it.
The fill logic — src/lib/engine/engine.ts
Entry and exit prices, slippage, which side pays the fee, the rule that a stop beats a target inside one bar, gap-aware fills, liquidation, funding. The public board's record was produced by exactly this arithmetic, so a copy with edited fills is a different product wearing the same record.
The rules already in the risk guard — src/lib/engine/guard.ts
ADDING a house rule is invited and the customisation guide shows where. Removing one is not: the symbol whitelist, the closed-market refusal, the cash and margin checks, and the sequential walk that makes close-then-reopen legal while a double-open is not.
The honesty detectors — src/lib/analysts.ts, src/lib/roster-copy.ts, src/lib/record.ts, src/lib/lanes.ts
They refuse advice, prediction, an amount made or lost and a ranking of desks in any generated write-up, and they compute the luck headline instead of asserting it. Every arm was added after a specific false claim reached a live screen, so when one fires the answer is to fix the text.
The read-only fence — src/lib/viewer-fence.ts
Every mutating API route opens with the fence as its literal first statement. Add a route with a POST, PUT, PATCH or DELETE and skip that line, and a public board of yours ships with a writable hole in it.
Anything that would make it claim performance
The no-claim block in README.md, the refund definition in START-HERE.md, the negative results under data/probe/, and LICENSE.md and NOTICE. Reword any of those and the archive stops matching what was sold.
None of this is invented for a web page. It is the first thing the archive’s own AGENTS.md and CLAUDE.md say to an agent that opens the folder — the same contract this board was built under.
3 of your own desks at once, and a desk may only leave flat.
Two rules that are easier to read before you plan a team of ten than after. One is a cost brake; the other is the one place here where getting it wrong could cost you simulated money standing in for a real position.
- 3 desks created from the board may be live at once. Retire one to make room. Desks you write into the config by hand are not counted — the cap is on the flow that spends money on your behalf without you opening a file.
- Ids are never reused. Every id ever created is recorded and burned, and the board refuses a name that resolves to one of them. An id is the database key, so reusing one would adopt that desk’s entire history and equity instead of starting a fresh $10,000 account.
- A desk may only be retired flat, with nothing resting. An open position or a resting order refuses the retirement, and the check is repeated at the last possible moment because a limit can fill between the two.
The reason is worth reading twice. Stops, targets, trails, liquidation, funding and pending fills are settled by the monitor tick, and the monitor settles every desk that is IN the configuration. A desk removed from the config is not settled by anything: its open position would run past its own stop and past its own liquidation price with nothing left to enforce either.
Switching a desk off is a different act, and a safe one. A desk with "enabled": false stops deciding and stops being ranked, but stays in the configuration — so its open book is still marked, still stopped out, still liquidated, still funded, every tick. Switching a desk off stops it taking new risk; it does not abandon the risk it has.
Part three — what it does without you
What happens in the 4 hours you are not watching.
Every desk decides on the same clock, on the same snapshot, and every order it returns walks the same five stages. That order is not cosmetic: the guard sits between the model and the money, and no desk can decline to be checked.
- Gold
- GC=F
- Silver
- SI=F
- Euro / dollar
- EURUSD=X
- Crypto, on one desk
- BTC-USD · ETH-USD · SOL-USD · XRP-USD
All of them are tickers on a keyless public feed, which is why a first round works before you have signed up to anything.
Snapshot
Prices and indicators for every symbol, plus this desk's own account, its drawdown and exposure, its career record per symbol, its last closed trades and its recent turns — including the orders the guard rejected and why. Every desk in a round is handed the same snapshot, so a desk late in a long walk never reads a fresher market than one early in it.
Decide
The desk answers in a strict schema: orders to open, close, update or cancel, plus its reasoning. It may cross at market and pay the taker fee and slippage, or rest a limit order and pay the maker fee with no slippage — patience is literally cheaper. Garbage is rejected by the schema, retried once with the validation error, and otherwise recorded as an error turn; the account is untouched and the board carries on.
Risk guard
Every order passes through the house rules before the engine sees it: symbol whitelist, one position per symbol, leverage cap, per-position and total margin caps, stop and target sanity, cash check. Rejections carry their reason, and the desk is shown that reason in its next snapshot.
Execute
The paper engine fills what survived at price plus or minus slippage, charges fees both sides, applies funding where the venue publishes it, and watches stops, targets and liquidation on price extremes between rounds. When a market is closed and the last print has gone stale, it refuses to fill at a frozen price at all.
Persist
The full prompt and the raw response are stored with the turn. That is what makes the log auditable rather than a summary: you can read exactly what a desk was asked and exactly what it answered, months later.
Between rounds a monitor tick runs about every two minutes: it marks open positions to market, fires stops, targets and liquidations, applies funding and appends the equity history the charts draw. The board is never idle even though it is usually quiet — a fresh one looks frozen when it is only waiting.
Its own playbook, with a date on every idea.
Every rule cites three dated journal entries; every hypothesis carries a kill condition and the date it dies on.
The overview quotes what one desk actually wrote on its first turn. This is what it was required to write — the contract the harness injects into every turn, with its caps intact. It is why those files are evidence of a rule rather than of a mood, and why all three (PLAYBOOK.md, JOURNAL.md, decision.json) are committed to a git repository the desk owns, one commit a turn.
- PLAYBOOK.md — at most twelve rules, each citing three dated journal entries; fewer than three and it is a hypothesis, not a rule. At most three hypotheses, each carrying a kill condition and the date it dies on. One rule may change per turn, on evidence.
- JOURNAL.md — one line per turn, appended before the decision: what the last turn produced, what this turn did and why, and the rule it acted under. Facts only, never rewritten, never deleted.
- A post-mortem per closed trade — the plan in its own words, what happened, and a verdict of process-correct win, process-correct loss, or process error. A loss that followed the plan is not an error and the desk has to say so. Each closes on a question for its future self.
- FEEDBACK.md — the only channel from you to the desk. You write into it, the desk acts on it and clears the file. The playbook and the journal are the desk's own: a human editing them destroys the record they exist to be.
There is a review turn built in as well. When the journal shows no recent review, the desk reads its whole journal and playbook before deciding anything: count what supports and what contradicts each rule, demote the ones that have stopped earning their citations, kill any hypothesis past its kill-by date, and write down what it kept, demoted and killed.

What this produces is a record, not a result. A desk that writes a good playbook is not thereby a desk that makes money — this product's own study found no edge at all, and it ships in the zip.
Run the health check first. Always.
One command, before anything else, whatever the symptom. It checks the runtime, the configuration it loaded, which desks have a credential, whether prices can be fetched and whether the database can be written — and it names the line to fix.
Two spellings, one script: the archive’s own documents say pnpm check and pnpm run doctor is an alias for the same file. What does not work is the bare pnpm doctor — pnpm ships a built-in of that name which shadows a package script completely, so that spelling exits without an error and looks exactly like a health check that passed.
Nothing is happening
Check that the runner window is still open, then check the clock. A round is every four hours by default, so a still board is usually a board waiting rather than a board broken. The board serves the screens; the runner is what decides. If the last round keeps ageing and the countdown has passed, the runner is not running.
A desk is sitting out
A desk with no key does not stop the round: it records a turn naming the variable it wanted, and the others trade on without it. A desk whose local model or agent CLI is unreachable does the same. The health check names which — and node scripts/keyless.mjs writes a copy of your board with every one of them switched off, beside your database, leaving your own config alone.
Something says FAIL
Run pnpm run doctor and read the row. Then the archive's troubleshooting section, where every entry begins with the exact text you are looking at and answers it with a command to paste or a file to open. If it is still wrong, write to support with that output pasted in — it prints key presence, never key values.
The archive carries a troubleshooting section where every entry begins with the exact text you will see on your own screen and answers it with a command to paste or a file to open. If that does not settle it, write to support@pulltrade.app with the health check’s output pasted in. It prints key presence, never key values, so it is safe to send.
What counts as a defect is written down rather than left to a mood: an archive that is corrupt or missing a file it lists, an install that fails on a supported runtime, a test suite that does not go green on a clean install, or a health check reporting FAIL for a reason inside the product rather than inside your machine. Losing simulated money is not a defect, and neither is a desk deciding badly — nothing here claims an edge, and this product’s own study found none.
