Replay history at 100×, decide each step, get a causality receipt
1. Mint a replay session
curl -s -X POST https://api.taifoon.dev/tape/v1/sessions \
-H 'Content-Type: application/json' \
-d '{
"from": "2026-05-15T00:00:00Z",
"to": "2026-05-15T04:00:00Z",
"markets": ["BTC-PERP"],
"speed_x": 100.0
}' | jqThe response includes session_id, the resolved t_logical cursor, and a per-market partitioned book seeded from the matcher state at from.
2. The decision loop
Each tick: read the candles up to the current logical time, read the snapshot, hand both to your model, parse a decision, submit it. Pseudocode:
def loop(session_id):
while True:
clock = get(f"/tape/v1/clock?session={session_id}")
if clock["done"]: break
candles = get(f"/tape/v1/candles?market=BTC-PERP&tf=1m&to={clock['t_logical']}&limit=200&session={session_id}")
depth = get(f"/book/v1/depth/BTC-PERP?session={session_id}")
prompt = render_prompt(candles, depth)
decision = llm.complete(prompt) # {side, qty, price, reason}
if decision["side"] != "noop":
post(f"/book/v1/orders", json={
**decision,
"market": "BTC-PERP",
"tif": "ioc",
"attribution": f"llm-{session_id[:8]}",
"session": session_id,
})
post(f"/tape/v1/sessions/{session_id}/step") # advance one tick3. Submit decisions back
Order placement during a warp session is identical to live placement — same endpoint, same body — except you pass session: <sid> so the matcher routes into the partitioned book. Fills resolve against replayed counterparty quotes; nothing leaks to live state.
4. Read the receipt
curl -s https://api.taifoon.dev/tape/v1/sessions/<sid>/receipt | jq
The receipt is the 3-actor schema: decider (your LLM prompt + response), matcher(the order/fill stream the matcher produced), trader (the attribution string). The top-levelcausality_ok bit is true iff every matcher.order_id traces back to a uniquedecider.decision_id. A false bit means you have an order with no upstream LLM call — almost always a retry bug.
5. Promote to live
Same gate as the MM track: 24h paper WR ≥ 85% on n_resolved ≥ 30 fills. Then drop the session from your client and target live (omit session). The receipt schema is identical in live mode; onlyspeed_x changes from your warp speed to 1.0.
noopstill produces a row and still incurs the (small) metering fee. See /clob/docs/operators/llm.