# Review-bot scenarios

Two scenarios that hold a working market-maker bot as diffs and pull requests that each break it one way, for testing a review agent.

`github-pulls_bot_pr_broken` and `github-pulls_bot_pr_queue` hold a Kalshi market-maker bot and pull requests that break it in known ways. A review agent reaches the bot only as diffs, exactly as it meets any code in a pull request. Load the first, read the working bot from the merged pull request and the one-line break from the open one:

```bash
H='Authorization: Bearer ghp_anything'
R="$RYSTIC_URL/repos/tb-owner/tb-repo"
curl -s -X POST "$RYSTIC_URL/_rystic/scenario" -d '{"name":"github-pulls_bot_pr_broken"}' -o /dev/null
curl -s "$R/pulls?state=all" -H "$H" | jq -c '[.[] | {number, state, merged: (.merged_at != null)}]'
# -> [{"number":8,"state":"open","merged":false},{"number":7,"state":"closed","merged":true}]
curl -s "$R/pulls/7/files" -H "$H" | jq -c '[.[] | {filename, additions}]'
# -> [{"filename":"bots/kalshi_mm/bot.py","additions":158}]
curl -s "$R/pulls/7/files" -H "$H" | jq -r '.[0].patch' | head -1
# -> @@ -0,0 +1,158 @@
curl -s "$R/pulls/8/files" -H "$H" | jq -r '.[0].patch' | head -1
# -> @@ -140,7 +140,7 @@ class MarketMaker:
curl -s "$R/check-runs/6508/annotations" -H "$H" | jq -c '[.[] | {path, start_line, annotation_level}]'
# -> [{"path":"bots/kalshi_mm/bot.py","start_line":143,"annotation_level":"failure"}]
```

Strip the leading `+` from #7's patch and you have `bot.py`; apply #8's hunk and you have the broken head. #8 carries a red `bot-smoke` check run that annotates the broken line.

### Running the bot

Run either file against a Kalshi simulator. It needs Python's `requests` package:

```sh
pip install requests
KALSHI_BASE_URL=http://localhost:8081 TICKER=KXDEMO-26-A SESSION_SECONDS=30 python3 bot.py
```

`bot.py` rests a bid and an ask behind the touch on one market, re-quotes as the book moves and never crosses. It stops quoting a side once its position reaches `MAX_INVENTORY`, though a fill can carry it past by up to `QUOTE_SIZE`. It cancels everything at `SESSION_SECONDS` (default 30) or on SIGTERM. It reads its settings only from the environment: `KALSHI_BASE_URL`, `TICKER`, `QUOTE_SIZE`, `MAX_INVENTORY` and the others its docstring lists.

The same files ship as plain text in the release archive: `bots/bot.py`, `bots/variants/*.py` and `bots/test_bot.py`. The container image does not carry them. The scenario's patches are generated from those files, so the diffs and the plain text cannot disagree.

`test_bot.py` runs the working bot for 30 seconds against `kalshi-twin --scenario tight_book` and requires a bid and an ask resting at once, no crossed quote and nothing left resting. It then runs every variant and requires each to fail that. It boots the `kalshi-twin` that `rystic pull` installed, or the binary `KALSHI_TWIN_BIN` names, or uses the simulator already serving at `KALSHI_BASE_URL`, and loads `tight_book` before each bot.

### The variants

`github-pulls_bot_pr_queue` holds the same merged bot, then five open pull requests (#11 to #15) that each break it a different way:

| Open pull request | Variant | What breaks | Result on the Kalshi simulator |
|---|---|---|---|
| #8 and #11 | `sides_inverted` | the buy leg goes out as `ask`, the sell leg as `bid` | `400 invalid_order: post only cross` |
| #12 | `syntax_error` | `def requote(self)` lost its colon | the file does not compile |
| #13 | `wrong_endpoint` | orders posted to `/trade-api/v2/orders` | `404 not_found` |
| #14 | `price_in_cents` | `"38"` where `"0.38"` was meant | `400 invalid_price` |
| #15 | `missing_required_field` | `self_trade_prevention_type` dropped from the body | `400 missing_parameters` |

Every one of them has its own red check run and line annotation. Use them to test a grader or a review agent: approving any of them means it missed the bug.

---

Source: https://www.rystic.ai/docs/simulators/github-pulls/features/bot-scenarios · Markdown: https://www.rystic.ai/docs/simulators/github-pulls/features/bot-scenarios.md
