An RL environment that tests whether AI agents understand prices. The agent runs a freight forwarder for a 10-week season and buys ship space in sealed-bid auctions against other shippers who price like economists.
The idea
A ship that carries paper from Kotka to Lübeck has to sail home anyway. With far more space than cargo on the way back, that space sells for little more than the carriers' $4/t loading cost. In our market it clears at about $5/t. Eliezer Yudkowsky saw an AI model price such a trip home at $20/t by charging it for fuel both ways.
Every frontier model we tried gets that part right. Where they lose money is modelling the other bidders. Every winning bid pays its own price, and the other shippers bid what they expect the space to clear at, which can be well below its value to them. In week 3, while a pulp mill was shut, Kimi K3.1 Pro bid $30.01/t for space that cleared at $5.22 and paid $61,765 for 2,000 t. Its notes: If uniformly valued30-70 and bids equal values, marginal should35.714.
Results
The score is the season's cash, scaled so that doing nothing scores 0 and the best season possible with full knowledge of every rival bid, solved exactly as a mixed-integer program, scores 1. These runs used market seed 1.
What the agent faces
- Five MCP tools:
market_brief,get_status,accept_offer,place_bids,advance_week. - A published outlook of ship capacity and other shippers' demand for every week, plus news: a pulp-mill shutdown (weeks 3–4), a ship in drydock (6–7), a holiday surge (8–9), a 60% fuel price rise (week 5) and an extra ship announced in week 6.
- Pay-as-bid auctions filled from the highest bid down, so an agent's own high bids take space ahead of its low ones.
- Customer offers to accept or decline, worth taking or not depending on where prices will be.
Reward integrity
- The market runs in its own container. The agent reaches it only through the five tools, and the score endpoint needs a token held by the checker.
- Each container draws a random market seed, so reading this source does not reveal the rival bids.
- The agent runs as an unprivileged user. The checker kills its leftover processes and runs Python in isolated mode before reading the score.
- Unplayed weeks are played out with no bids, so stopping early costs the penalties on unshipped cargo.
Run it
Requires Docker and Harbor (uv tool install harbor).
curl -O https://backhaul.jc.iffe.eu/backhaul.tar.gz tar xzf backhaul.tar.gz && cd backhaul ./eval.sh -a opencode -m anthropic/claude-opus-5-5
eval.sh runs the reference, the do-nothing and stop-early baselines and three agent runs, and prints each reward. Set BACKHAUL_SEED=1 to replay the market behind the results above.