# Host your own bot

> Deploy your own Kalshi bot code from a directory, a script or a GitHub repository; every deploy is a preview on Langbot's paper exchange, and promoting it runs it on your own Kalshi account behind your guardrails.

Source: https://langbot.ai/docs/hosted-code

Bring your own code. Write a Kalshi bot in Python, Node, Rust, Go or anything with a
Dockerfile, and Langbot builds it, runs it in a sandbox and points it at the right exchange:

- every deploy starts a **preview** on Langbot's **paper exchange**: Kalshi's own API, on the
  order books Langbot records live, with Kalshi's fees. No real money.
- **promote** a preview and it becomes a **live** deployment on **your own Kalshi account**.
- **roll back** to an earlier live deployment, or **stop** the bot (the kill switch), with one
  command or one click.

The same code runs on both, unchanged: it reads `KALSHI_API_BASE`, `KALSHI_API_KEY_ID` and
`KALSHI_PRIVATE_KEY` from its environment and talks Kalshi's Trade API v2.

> **Your money stays on your Kalshi account**
>
> Langbot never takes deposits, never pools funds and never trades at its own discretion: a
> bot trades only the code you wrote, only on your own account, and only after you promote
> it. Hosted bots are priced per bot on your subscription, never per trade and never as a
> share of profit.

## Start in a minute

```bash
langbot bots init wx-bot --template python    # or --template node
cd wx-bot
langbot bots deploy .                          # build + a preview on the paper exchange
langbot bots logs wx-bot -f                    # stdout, stderr and Langbot's own lines
```

The template is a small market maker for one Kalshi series: it lists the open markets, keeps one
small bid a cent above the best bid where the spread is wide, and cancels what it no longer
wants. Change `decide()` to make it yours.

Three ways to give Langbot your code:

| Source | How | What a change does |
|---|---|---|
| A project directory | `langbot bots deploy .` | a new build, then a new preview |
| One script | `langbot bots deploy bot.py` | the same, from a single file |
| A GitHub repository | `langbot bots connect-github wx-bot owner/repo` | every push and pull request builds a preview; the production branch builds the **production candidate** |

`.env` files and private keys (`*.pem`, `*.key`) are never uploaded; the CLI says which files it
left out. Add patterns to `.langbotignore` to leave out more.

## langbot.yaml

```yaml
name: wx-bot
runtime: python            # python | node | rust | go | docker
entrypoint: bot.py         # or: command: ["python", "-m", "wxbot"]

markets:                   # what the bot may trade; orders anywhere else are refused
  series: [KXHIGHNY]       # also: tickers, events, query, category, speaker, kind, calendar

budget_usd: 50             # the paper balance on previews; the most at risk on live

limits:                    # guardrails, enforced outside your code
  max_order_contracts: 10
  max_order_usd: 10
  max_daily_loss_usd: 12.5
  max_orders_per_min: 10
  stop_after_errors: 3

env: [OPENWEATHER_KEY]     # secrets your code reads (values: `langbot bots env set`)
egress: [api.open-meteo.com]   # extra HTTPS hosts it may reach
order_proxy: true          # live orders go through Langbot's order proxy (the default)

resources: {cpu: 0.5, memory_mb: 512}
schedule:                  # optional: when it may open positions
  windows: recurring
  recurring: [{days: [mon, tue, wed, thu, fri], start: "09:00", end: "16:00"}]
processes:                 # optional: more processes beside the main one
  feed: {command: ["python", "feed.py"]}
health: {type: http, port: 8080, path: /healthz}   # optional; default: the process is alive
restart: {policy: on-failure, max_restarts: 5, window_s: 600}
```

A guardrail is turned off by setting it to `null` **and** naming it under `confirm_off` (you are
saying the bot may lose its whole budget). A single script can carry the same settings in a
comment block, and a Python script's dependencies in a standard `# /// script` block:

```text
# /// script
# dependencies = ["httpx>=0.27"]
# ///
# /// langbot
# markets: {series: [KXHIGHNY]}
# budget_usd: 25
# ///
```

### How it is built

A `Dockerfile` in the project is built as written. Otherwise Langbot detects the runtime from
your files and writes the image for you: `requirements.txt` or `pyproject.toml` (Python),
`package.json` with its lockfile (Node), `Cargo.toml` (Rust), `go.mod` (Go). Every image runs as an
unprivileged user with a read-only file system; write to `/tmp`. The build log streams to the
CLI, the web page and `langbot bots logs --build bld_…`.

## What your bot runs with

| Variable | Preview | Live |
|---|---|---|
| `KALSHI_API_BASE` / `KALSHI_WS_URL` | the paper exchange | Langbot's order proxy (or Kalshi, with `order_proxy: false`) |
| `KALSHI_API_KEY_ID` / `KALSHI_PRIVATE_KEY` | a paper key Langbot issued | a proxy key (or your own restricted Kalshi key, with `order_proxy: false`) |
| `KALSHI_ENV` | `paper` | `prod` or `demo` |
| `LANGBOT_API_TOKEN` / `LANGBOT_API_BASE` | a token scoped to this bot: it reads Langbot data and this bot only | same |
| `LANGBOT_BOT_ID`, `LANGBOT_DEPLOYMENT_ID`, `LANGBOT_TARGET` | ids, `preview` | ids, `live` |
| your `env:` names | the values you set | the values you set |

`KALSHI_PRIVATE_KEY` is a PEM; if your platform flattened it, replace a literal `\n` with a newline
before loading it (both templates do).

Env values are write-only: `langbot bots env set wx-bot OPENWEATHER_KEY` prompts for the value,
seals it, and nothing ever shows it again. Every log line is scrubbed of your secrets before it
leaves the sandbox host.

## The paper exchange

The paper exchange speaks Kalshi's Trade API v2: the same paths, the same request signing (RSA-PSS
or Ed25519, `KALSHI-ACCESS-KEY` / `-TIMESTAMP` / `-SIGNATURE`) and the same response shapes for
markets, events, order books, balance, positions, fills, orders (create, amend, decrease, cancel,
batches) and the WebSocket channels (`orderbook_delta`, `ticker`, `fill`, `user_orders`).

Fills follow the Simulator's model, on the books Langbot records live: a marketable order takes
the recorded depth up to its limit, a resting order fills when a later book trades through its
price, and fees are Kalshi's (taker 7%, maker 1.75% of `C·P·(1−P)`, rounded up to the cent). What
differs from Kalshi: there is no market impact and no queue position, liquidity your order took
returns with the next recorded book, markets settle on the results Langbot records, and rate
limits are not enforced. Each preview has its own paper balance (its `budget_usd`).

Run a bot on your own machine against it with a paper key:

```bash
langbot bots paper-key > .env.paper     # KALSHI_API_BASE, KALSHI_API_KEY_ID, KALSHI_PRIVATE_KEY
set -a; . ./.env.paper; set +a
python bot.py
```

## Going live

1. Connect your Kalshi key on the bot's page while signed in (the same one-step setup as
   strategy bots: a sub-account with the budget moved into it and a restricted, trade-only key).
   Keys are never accepted from the CLI, the MCP or chat.
2. `langbot bots promote wx-bot` (or **Promote** on the page). Langbot promotes the production
   candidate when there is one, else the newest preview; `-d hd_…` picks one.

Live orders go through **Langbot's order proxy** by default. Your bot signs its requests with a
proxy key; the proxy checks each order against your `markets` and `limits` (contracts, dollars at
risk, orders per minute, daily loss, the budget, errors in a row), writes it to the bot's audit
log, and only then signs it with your Kalshi key and sends it on. Your Kalshi key never enters the
sandbox, and your code cannot get past your guardrails. Cancels and reads always pass, even after
a guardrail has stopped the bot. With `order_proxy: false` your code gets your restricted Kalshi
key and talks to Kalshi directly; the guardrails are then your code's own.

## Every deployment, kept

Each deploy is an immutable deployment (`hd_…`) with its own image, settings, logs and paper or
live ledger. A new preview replaces the running preview; a promote or rollback replaces the
running live deployment.

```bash
langbot bots deployments wx-bot          # deployments, builds, env names
langbot bots rollback wx-bot hd_…        # run an earlier live deployment again
langbot bots stop wx-bot                 # the kill switch (--target live|preview)
```

Stopping kills the bot's sandboxes within seconds and the order proxy refuses its new orders at
once. Orders already resting on Kalshi stay there: cancel them on Kalshi if you want them gone.

A bot that keeps crashing is restarted with a growing delay; more than `max_restarts` restarts
within `window_s` stops the deployment (`failed`, `crash_loop`) so a broken bot cannot hammer the
exchange.

## Sandbox

Each deployment runs in its own sandbox with a user-space kernel between your code and the
host, a read-only file system and a size-capped `/tmp`, no Linux capabilities, and CPU, memory and
process limits. It has no network of its own: it reaches only the paper exchange (previews) or
the order proxy (live), the Langbot API, and the HTTPS hosts you list under `egress:`. It can
never reach Langbot's internal services or other users' bots.

## From Claude and other agents

With hosted code bots enabled, the MCP server has `code_bot_init`, `code_bot_deploy`,
`code_bot_status`, `code_bot_logs`, `code_bot_promote`, `code_bot_rollback`, `code_bot_stop`,
`code_bot_env` and `code_bot_connect_github`. Promote and rollback trade real money and need the
user's explicit yes.
