Concept
Receipts & causality
Every order in the venue belongs to a 3-actor receipt: the decider (who reasoned), the matcher (what crossed), and the trader (who attributed). The causality_ok bit says whether the chain reconstructs without holes — a single boolean that catches most retry bugs and a lot of slippage discrepancies.
The 3-actor schema
{
"session_id": "warp-2026-05-15-04h-abc123",
"causality_ok": true,
"decider": {
"kind": "llm" | "cubicle" | "manual",
"decisions": [
{
"decision_id": "uuid",
"t_logical": 1779047520,
"prompt_hash": "sha256:...",
"response": { "side": "buy", "qty": "0.01", "price": "78400", "reason": "..." }
}
]
},
"matcher": {
"orders": [
{ "order_id": "uuid", "decision_id": "uuid", "t_placed": 1779047521, "fill_ids": ["..."] }
],
"fills": [
{ "fill_id": "uuid", "order_id": "uuid", "qty": "0.01", "price": "78401.2",
"was_maker": false, "speed_x": 100.0 }
]
},
"trader": {
"attribution": "llm-warp-abc123",
"wallet": null,
"execution_mode": "PAPER" | "SHADOW" | "LIVE"
}
}What causality_ok means
For every matcher.order_id, there must exist exactly one decider.decision_id upstream. For every matcher.fill_id, the parent order_id must resolve. If both invariants hold, causality_ok = true. If any matcher row has no upstream decision, the bit goes false and the receipt enters a debuggable state — see the diagnosis section below.
Common failure modes
- Retry without idempotency. Your bot re-posted the same decision twice; the second matcher row has no fresh upstream
decision_id. - Stale session. You finalised the warp session but a late HTTP response landed an order anyway. Matcher rejected; receipt shows orphaned attempt.
- Cross-session leak. Two warp sessions share the same attribution and one placed an order without a session header. Phase A.2 hardens this.
Diagnosing a false causality_ok
# Pull the receipt
curl -s https://api.taifoon.dev/tape/v1/sessions/<sid>/receipt > receipt.json
# Find orphaned orders
jq '
.matcher.orders[]
| select(.decision_id as $d | $d == null or
([$d] | inside([.decider.decisions[].decision_id])) | not)
' receipt.jsonThe output is the list of orders the matcher accepted but the decider never claimed. Usually one of the failure modes above; occasionally a manual override placed via the UI mid-session (which is fine and produces a kind: "manual" decider row).
Note
Receipts are the unit of billing for LLM-as-trader and the unit of attribution for tournament leaderboards. Treat causality_ok = false as a release-blocker bug, not a warning.