# Runner trader — setup

Runbook v0.2.0. Follow it top to bottom on your own computer. The
person you work for pasted one line; everything else is here. It is the
same runbook for every agent: where agents differ, it says what to do
depending on what you can do, and you decide which applies to you.

> **STATUS — RELEASED.** The `trader` binary is pinned in step 2.
> Install the row for this computer. If your row reads `UNRELEASED`,
> this computer is not in the release: stop after step 1 and give
> the evidence receipt (step 12) with every later step marked "not run —
> binary not released".

## Where it runs

The engine is a process that has to keep running. It runs up to three
engines, each turned on in one file (step 6): it can **paint** a Curve on
the book, **answer RFQs** with signed quotes, and **send RFQs** and take
the best quote. Paint stays on the book if the process stops; RFQ answers
expire with their TTL. Before step 1, decide which of these your computer
is, and tell the person:

| Your computer | Fit | Why |
| --- | --- | --- |
| A cloud VM or server that stays up | Best | Runs all day; `trader service install` supervises it (step 10). |
| A persistent computer your agent platform gives you | Good | Survives between chats; a scheduled task runs the watchdog (step 10). |
| The person's laptop or desktop (Linux, macOS, or Windows through WSL2) | Fine to try | Stops when it sleeps or shuts down; paint stands until the next watchdog run after it wakes. |
| A sandbox that is reclaimed when idle | No | Nothing survives it. Set up here only if the engine will run somewhere else. |

`trader` runs on Linux (x86_64, aarch64) and Apple Silicon macOS; on
Windows, install WSL2 and run every step inside it. Step 2 says which
builds this release has.

## What you are setting up

Runner is a physically-settled options order book on Solana. Options are
fully collateralized: covered calls backed by the token, cash-secured puts
and bids backed by USDC. There is no margin and no liquidation.

One binary, `trader`, does all of it: key, funding checks, registration,
three engines, safety, reporting. Which engines run, and every budget, is
one file, `~/.runner/trader.toml`:

| Engine | `trader.toml` table | Does | If the process stops |
| --- | --- | --- | --- |
| paint | `[paint]` | Keeps a Curve on the book for each token in `markets`, sized as a share of what is deposited. | Quotes stay on the book until pulled. |
| RFQ maker | `[rfq.maker]` | Answers requests on Runner's RFQ relay with signed quotes, inside its notional budget. | Answers die with their TTL. |
| RFQ taker | `[rfq.taker]` | Sends requests on the relay and takes the cheapest quote (or, with `max_iv_rv`, only one cheap against the token's realized volatility), on what its `strategy` picks: it buys options, and spends at most `daily_budget_quote` in any 24 hours. | Nothing is left open. |

You are its front end. You run its commands, read their JSON, and tell the
person what happened. You do not write transactions yourself and you do
not hold the key in your context.

Start from `runner://docs/market-makers` on the MCP server if the person
asks how the venue works.

## Rules that hold for every step

- **The key stays on this computer until the person asks for a backup.**
  `trader keygen` writes it to `~/.runner/keys/trader.json`, mode 0600.
  Do not print it, copy it, or send it anywhere — including Runner's —
  unless the person asked. When they do, ask whether they want a file or
  the key shown, then run `trader export --out <path>` or `trader export
  --print`. That is an approval. Show only the public address otherwise.
- **This wallet is hot and can withdraw.** Runner has no trade-only key:
  the key that quotes is the key that withdraws. So the wallet holds only
  what it trades with, and the person's savings stay in their own wallet.
- **Funds go out to one address only:** the main address the person pins
  in step 5. `trader sweep` sends there and nowhere else. Never use any
  other tool to move funds from this wallet.
- **Every change to `trader.toml` is an approval** (the gate, step 10),
  as is `trader sweep`, `trader swap` and `trader export`.
- **Paint lives on the book; RFQ answers do not.** Runner curves have no
  expiry: if the engine or this computer stops, paint stays at its last
  price until someone pulls it. The watchdog and `pull_on_exit` exist
  because of resting paint. Say that to the person before a mainnet run
  that paints.
- **The taker spends money on purpose.** With `[rfq.taker]` on, the bot
  buys options every hour, up to `daily_budget_quote` a day. Say that, with
  the number, before it runs.
- **Every command takes `--json`.** Always pass it, and decide from the
  JSON, not from the prose.
- **The config is `~/.runner/trader.toml`.** A `trader.toml` in the
  working directory is never read unless named with `--config`.

## Reading `trader` output

Every command with `--json` prints one JSON object on stdout:

```json
{
  "ok": true,
  "command": "fund-check",
  "version": "0.2.0",
  "network": "mainnet",
  "data": { },
  "error": null
}
```

On failure `ok` is false and `error` is
`{ "code": "...", "message": "...", "hint": "..." }`. Report `message`
and follow `hint`. Exit codes:

| Exit | Meaning | What you do |
| ---: | --- | --- |
| 0 | Done, or already done. | Continue. |
| 1 | Failed. | Report `error.message`; stop the step. |
| 2 | Bad flag or config (`trader.toml` included). | Fix the flag or the file; do not retry unchanged. |
| 3 | Waiting on the person (funds short, an uninstall to confirm). | Tell them exactly what `data` says is missing; rerun when they say it is done. |
| 4 | Refused by policy (an unpinned address, a network mismatch, already running). | Report it. Do not work around it. |
| 5 | The API did not answer. | Wait 30 seconds and rerun, up to three times; then report. |

Every command is idempotent. Rerunning one that already succeeded reports
what exists and changes nothing.

Configuration is read in this order, first wins: flags, then environment
variables `TRADER_*` (`TRADER_HOME`, `TRADER_CONFIG`, `TRADER_NETWORK`,
`TRADER_API_URL`, `TRADER_KEYPAIR`), then `trader.toml`, then the
network's defaults.

## 1. Connect the Runner MCP server and ring the opening bell

The MCP server teaches the protocol and shows live, read-only market
state. It needs no key and can sign nothing.

Add a custom MCP server, however your platform does it:

- Name: `runner`
- URL: `https://mcp.userunner.fun/mcp`
- Transport: streamable HTTP. No authentication.

Some agents take a command (for example `claude mcp add --transport http
runner https://mcp.userunner.fun/mcp`), some a JSON server list
(`{ "mcpServers": { "runner": { "type": "http", "url":
"https://mcp.userunner.fun/mcp" } } }`), some a settings page or a request in
the chat. If yours cannot add one, skip it: it is a convenience, every
step below works without it, and `trader markets --json` gives the opening
bell instead.

Then ring the opening bell: a read-only snapshot, before anything is
installed.

1. Call `list_markets`. Keep the Active ones.
2. For up to three of them, call `get_market` and `list_strikes`, and for
   the strike nearest spot, `get_orderbook` on each side.
3. Show the person one table:

| Market | Product | Spot | Strike | Best bid | Best ask | Makers |
| --- | --- | ---: | ---: | ---: | ---: | ---: |

Give numbers at the precision the tools return. If the Inspect tools are
unavailable, say so, and carry on: they are a convenience, not a
dependency.

| Result | Meaning | Next |
| --- | --- | --- |
| Tools listed, markets returned | Connected. | Step 2. |
| Server added, Inspect tools error | API side unavailable. Learn tools still work. | Step 2; note it in the receipt. |
| Server cannot be added | Your platform has no way to. | Step 2; use `trader markets --json` for the table. |

## 2. Install the pinned `trader`

The hashes below are pinned in this runbook. Download the binary for this
computer. If your `$target` row reads `UNRELEASED`, download nothing: stop
and go to step 12.

Pinned SHA-256 for v0.2.0:

| `$target` | File | SHA-256 |
| --- | --- | --- |
| `x86_64-Linux` | `trader-x86_64-Linux` | `e0421787b214ad4b07e695e94b4d69707d6a14beb61ae5f1a000f89e8818bbcf` |
| `aarch64-Linux` | `trader-aarch64-Linux` | `f2e5a61fb92d2c5a2bf523a3f37e3cff0db40cb6584ad7628ab353e05c31a45d` |
| `arm64-Darwin` | `trader-arm64-Darwin` | `UNRELEASED` |

Download the binary for this computer and compare its hash with the row
for `$target`. Do not fetch a checksum from anywhere else, and do not run
a binary whose hash does not match.

```sh
mkdir -p ~/.runner/bin
target="$(uname -m)-$(uname -s)"   # x86_64-Linux, aarch64-Linux, arm64-Darwin
curl -fsSL -o ~/.runner/bin/trader "https://assets.userunner.fun/bots/mm/v0.2.0/bin/trader-$target"
sha256sum ~/.runner/bin/trader     # macOS: shasum -a 256
chmod 0755 ~/.runner/bin/trader
export PATH="$HOME/.runner/bin:$PATH"
trader --version
```

| Result | Next |
| --- | --- |
| Hash matches, `trader --version` prints `0.2.0` | Step 3. |
| Hash differs | Delete the file. Stop. Tell the person the download did not match the pinned hash. |
| Your `$target` row reads `UNRELEASED` | Stop. Step 12. |

## 3. `trader init` and `trader doctor`

```sh
trader init --json
trader doctor --json
```

`init` creates `~/.runner/` (mode 0700, with `bin/`, `keys/`, `logs/` and
`state/`) and a starting `~/.runner/trader.toml`: `network = "mainnet"`,
no main address, and no engine on. Every key in it is commented. Nothing
signs on a network `trader.toml` does not name.

The key is generated on this computer by `trader keygen` (step 4) and
stays in `~/.runner/keys/trader.json`, mode 0600, owned by the user that
runs the engine. It is not typed into the chat and not copied into a
secret store, memory or notes.

The one-shot commands (`register`, `deposit`, `withdraw`, `swap`,
`sweep`, `pull`, `markets`, …) go through the Runner API at
`https://api.userunner.fun` and need nothing else. The engine, `trader
run`, also reads the API and its websocket, but it reads the chain and
sends its own transactions through a Solana RPC endpoint: `rpc_url` in
`trader.toml` (or `TRADER_RPC_URL`). Unset, it is Solana's public
endpoint (`https://api.mainnet-beta.solana.com`), which is rate-limited
and not meant for a bot that runs all day. Recommend the person get a
private RPC endpoint from a Solana RPC provider and set `rpc_url` to it
in step 6. An RPC URL often carries an API key: write it into
`trader.toml` and nowhere else, not notes, memory or a secret store, and
do not repeat it back.

`doctor` checks the computer and prints `data.checks`, a list of
`{ "name", "status": "pass" | "warn" | "fail", "detail", "fix" }`:

| Check | `fail` / `warn` means | Fix |
| --- | --- | --- |
| `version` | The binary's version. | Reinstall from step 2 if it is not `0.2.0`. |
| `home` | `~/.runner` missing or group/world readable. | `trader init` again, or `trader doctor --fix-perms`. |
| `key` | No key yet (`warn`), or the key file is group/world readable. | `trader keygen`; `trader doctor --fix-perms`. |
| `config` | `trader.toml` missing or refused (`fail`, the `detail` names the key), or no engine on yet (`warn`, expected until step 6). | `trader init`, or fix the key it names. |
| `rpc` | The API is unreachable, or cannot see the cluster. It tests the API's view of the chain, not your `rpc_url`. | Wait and rerun. If you set `TRADER_API_URL`, check it. |
| `program` | The API builds for another Runner program than this binary. | Wait and rerun; then reinstall from step 2. |
| `balance` | The wallet holds no SOL (`warn`, expected until step 7). | `trader fund-check`. |
| `api` | The API answered but the cluster is not ok (`warn`). | Wait and rerun. |
| `mcp` | MCP server unreachable. A `warn`, never a `fail`. | None needed. |
| `computer` | A `warn` when this computer may not last (a RAM-disk home, a container with no volume, a known sandbox or short-lived host), or is a laptop that sleeps. `detail` says which signal it read. | Back up the key; set a wind-down (step 6). |

Continue when nothing is `fail`. Report each `warn`. A `fail` that the
`fix` does not clear is worth one short feedback (see "Feedback"),
with the `trader doctor --json` output attached; `data.feedback` is the
command.

## 4. Generate the wallet and show its address

```sh
trader keygen --json
trader address --json
```

`keygen` creates the key on this computer if none exists, and reports
`data.address` and `data.created`. It does not print the private key. Run
twice, it reports the same address with `created: false`. A backup is
`trader export`, and only when the person asks (step 6).

Show the person `data.address` and say, in these words or close to them:

> This is the bot's own wallet. Its key was generated on this computer and
> has never been shown to anyone, including me. It can trade and it can
> withdraw, so send it only what you want it to trade with.

## 5. Pin the main address

Ask the person for their main Solana address: their own wallet, where
profits and anything left over go back. It must not be the bot's address.

```sh
trader init --main-address <ADDRESS> --json
```

This writes `main_address` into `trader.toml`, leaving every other line
as it was. It is the only address `trader sweep` and `trader uninstall`
ever send to, and changing it later is a `trader.toml` change (approval).
Read the address back to the person and have them confirm it character
for character.

| Result | Next |
| --- | --- |
| `ok` | Step 6. |
| Exit 4, `error.code: "same_as_bot"` | Ask for a different address. |
| Exit 2, `error.code: "invalid_address"` | Ask again; it is not a Solana address. |

## 6. Choose the engines and write `trader.toml`

Ask the person what they want the bot to do, and turn on only that. Read
them this table:

| They want | Turn on | Needs |
| --- | --- | --- |
| To earn premium by writing options, only when someone asks (the recommended start) | `[rfq.maker] enabled = true` | USDC deposited (puts and bids). For calls, `jit = true` to buy the token at the fill: base deposited to back calls on a token the paint does not run is sold back to USDC by the unwind (`[rfq.maker.unwind]`, on by default). |
| To keep quotes on the book for chosen tokens all day | `[paint] markets = ["<token>"]` | The same, and a computer that stays up (paint outlives the process). |
| To buy options on a schedule (flow for the makers, or their own exposure) | `[rfq.taker] enabled = true` | USDC for `daily_budget_quote`, which it spends. |

Any combination runs in one process, on one key and one trading account.
Read the `runner-trader-config` skill before the first edit; read the
`runner-trader-rfq` skill before turning either RFQ engine on. If the
person asks how to choose, read them `runner://docs/make-markets/strategy`
on the MCP server.

Edit `~/.runner/trader.toml` in place: change only the keys the person
chose, and leave every comment. The keys people ask about:

| Table | Key | Decides |
| --- | --- | --- |
| top level | `rpc_url` | The Solana RPC endpoint the engine reads and sends through (step 3). Set a private one. |
| `[paint]` | `markets` | Tokens to keep a Curve on, by mint or symbol. Empty paints nothing. |
| `[paint]` | `base_size_nav_frac`, `base_size_usd_cap` | Size per level, as a share of what is deposited, capped in dollars. |
| `[paint]` | `max_live_call_nav_frac`, `max_live_put_nav_frac`, `bid_enabled` | The most premium live on each side; whether it bids. |
| `[rfq.maker]` | `enabled`, `max_notional_home_per_token`, `max_notional_home_total`, `budget_frac` | Answers on or off, and the most notional answers may hold. |
| `[rfq.maker.filters]` | `min_mcap_usd`, `min_age_secs`, `deny_mints`, … | Which tokens it answers on at all. |
| `[rfq.maker.unwind]` | `enabled`, `min_home_value`, `max_slippage_bps` | Whether base and other tokens it holds on a token the paint does not run are sold back to USDC (on by default). |
| `[rfq.taker]` | `enabled`, `requests_per_hour`, `min_pay_quote`, `max_pay_quote`, `daily_budget_quote` | How often it asks, how much each request spends, the most a day. |
| `[rfq.taker]` | `strategy`, `max_iv_rv`, `avoid_makers`, the token and strike bounds | What it asks for (`random`, `vol`, `momentum`, `launch`), the deal test, and makers never filled against: the `runner-trader-rfq` skill's "Pick a strategy". |

`trader.toml` refuses every unknown key, anywhere: a misspelt cap never
quietly becomes the default. After each edit:

```sh
trader doctor --json        # the `config` check: parses, and which engines are on
trader markets --json       # what the painted tokens trade, with strikes and spot
```

Show the person the change as before → after, and the worst case: the
most it can write (paint and answers, against what will be deposited) and
the most the taker spends a day. On first setup that is the proposal they
approve; after it, every edit goes through the approval gate.

The engine's per-run flags (`--paint`/`--no-paint`, `--rfq-maker`/
`--no-rfq-maker`, `--rfq-taker`/`--no-rfq-taker`) override the file for
one run only. The supervisor (step 10) starts the engine from the file
alone, so what should keep running belongs in the file.

### Back up the key, and plan the end

Before any money arrives, offer a backup of the bot's key (`trader export
--print` to show it once for a password manager, or `--out <path>`), an
approval. On a computer `doctor` reads as temporary or unknown the backup
is required: when such a computer goes, paint is left on the book and
everything the bot holds is stranded with the key. With the backup, on any
other computer:

```sh
trader init --json
trader import --from <backup file> --json   # or --from - and paste it
trader uninstall --confirm --json            # or carry on there
```

On such a computer, ask when it goes away, and offer `trader winddown
--expires <+23h | ISO time>`: an hour before, the watchdog runs `trader
uninstall --confirm` by itself, sending everything to the main address.
Setting it is the approval for that uninstall, so say exactly that to the
person. `trader winddown --json` shows it; `--cancel` undoes it. If the
platform sends a shutdown signal first, the engine pulls its quotes on it,
but nothing short of the wind-down gets the funds out in time.

## 7. Fund the wallet

Ask the person how much USDC the bot should trade with: the paint and the
RFQ maker size off what is deposited, so that amount is their budget.
Then:

```sh
trader fund-check --json
```

`data.items` lists what the wallet needs before it can register and run
the engines `trader.toml` turns on:

```json
{
  "address": "…",
  "ready": false,
  "items": [
    { "asset": "SOL",  "have": "0", "need": "0.15", "status": "short", "why": "…" },
    { "asset": "USDC", "have": "0", "need": "50",   "status": "short", "why": "the taker's daily budget; the paint and the RFQ maker size off what is deposited" },
    { "asset": "BONK", "have": "0", "need": "0",    "status": "optional", "why": "backs the paint's call asks" }
  ],
  "send_usdc": 2.15,
  "sol_from_usdc": { "usdc": 2.15, "command": "trader swap --input USDC --output SOL --amount 2.15 --json", "ready_to_swap": false },
  "sol_note": "…",
  "deposit": { "address": "…", "links": [ … ], "qr": "…" },
  "routes": [ { "from": "cex", "how": "…" }, { "from": "evm", "how": "…" }, { "from": "swap", "how": "…" } ]
}
```

USDC reads `optional` unless the taker runs: what the paint and the RFQ
maker trade with is the budget the person just gave you, on top of
`send_usdc`. Ask for USDC only. When `data.sol_from_usdc` is set, the bot
buys its own SOL from the USDC: tell the person to send `data.send_usdc`
plus their budget, repeat `data.sol_note` (what the SOL is for), and once
it has arrived (`sol_from_usdc.ready_to_swap`) run `sol_from_usdc.command`,
an approval. While the wallet holds no SOL that swap is sponsored: Runner
pays its fee and is paid back in USDC. Otherwise, tell the person each
`short` item, with `need` exactly as printed, and the deposit address.
`data.deposit` has what they pay with: `qr` is the bot's address as a QR
code (show it in a monospace block; an exchange's withdrawal screen scans
it), and `links` holds a Solana Pay link per short asset, with the amount
filled in, for a wallet. Then the routes:

- **From an exchange (preferred).** Withdraw on the **Solana** network,
  straight to the bot's address. USDC withdrawn on any other network does
  not arrive.
- **From an EVM chain.** Native USDC through Circle's CCTP
  (https://www.circle.com/cross-chain-transfer-protocol), or a bridge
  aggregator the person already trusts. They do it from their own wallet;
  you never handle an EVM key.
- **The base token** (only for the paint's covered calls, on a token it
  paints): once USDC is in the bot wallet, `trader swap --input USDC
  --output <symbol> --amount <n> --json` converts it there. That is an
  approval. The person does not open an exchange to do it.

When they say it is sent, run `fund-check` again. Loop until `data.ready`
is true and the budget has arrived.

| Exit | Meaning | Next |
| ---: | --- | --- |
| 0 | `ready: true`. | Step 8. |
| 3 | Still short. | Tell them what is still short; wait. |
| 5 | The API did not answer. | Retry as in the exit table. |

`need` follows `trader.toml`. If the person changes tokens or engines
later, run `fund-check` again. For how `need` is computed, read the
`runner-trader-funding` skill.

## 8. Register, deposit, and rehearse the paint

The engines trade from the bot's TradingAccount, not its wallet: nothing
can run, not even a rehearsal, until the account exists and holds the
budget. Registering and depositing trade nothing. Tell the person that
this moves their budget from the bot's wallet into its trading account,
and that nothing quotes until step 9. Then:

```sh
trader register --json
trader deposit --asset USDC --amount <amount> --json
```

`register` creates the TradingAccount (32 slots) and a Curve for each
painted token, and reports `data.trader_id` and `data.created`; run twice,
it creates nothing. `deposit` moves tokens from the wallet into the
TradingAccount, and refuses (`protocol_paused`, exit 4) while the protocol
pauses deposits. `<amount>` is the budget the person gave you in step 7,
at most what they sent. Every command that signs exits 4,
`network_mismatch`, when `--network` and `trader.toml` disagree.

**Rehearse the paint.** With the account funded, run the paint against
live data with nothing sent:

```sh
trader run --dry-run --detach --json
```

A dry run runs the paint alone, whatever `trader.toml` turns on, and signs
no transaction: both RFQ engines stay off, because a signed answer is a
quote other people can take, and an acceptance the taker never lands
ties up the makers who answered. With no paint on, this exits 2, `no_engine`:
mark the rehearsal "not run — RFQ only" in the receipt; the RFQ engines'
first run is the live one in step 9.

Let it run for five minutes, then:

```sh
trader status --json
trader stop --json
```

| `data` field | Healthy in a dry run |
| --- | --- |
| `running` | `true` |
| `engines` | `["paint"]`: what this run resolved, not the whole file. |
| `mode` | `active` |
| `ready` | `true`. `false` says why in `health`; it is normal for the first minute. "holds no treasury" means the deposit has not landed. |
| `health` | The engine's own document; report anything it names as failing. |
| `quotes_live` | Unchanged, usually `0`: a dry run puts nothing on the book. |

The engine's log is `~/.runner/logs/trader.log`. Its first lines say
`sends disabled`; after that each send the paint would have made is
logged as refused (`sends disabled: this process signs no transaction`)
instead. Those lines are what it would have quoted. Read them, and read
them when anything above is not healthy.

## 9. Go live

This is an approval. Before you ask, tell the person:

- which engines run, on which tokens, and the budget of each (step 6);
- how much is deposited, and that the paint and the RFQ maker size off
  it;
- with the taker on, that it spends up to `daily_budget_quote` in any 24
  hours;
- with the paint on, that its quotes stay on the book if this computer
  stops, until the watchdog or someone pulls them.

After approval:

```sh
trader run --detach --json
trader status --json
```

`run --detach` returns `data.pid` and `data.log`; a second `run` exits 4,
`already_running`. `status` should read as in step 8, with `engines`
naming every engine the file turns on.

## 10. Keepalive, scheduled tasks and approvals

**Keep the engine running: `trader service install`.**

```sh
trader service install --json
```

It hands the engine from step 9 to the strongest supervisor the computer
has (stopping the detached one and starting it under the supervisor,
from `trader.toml`), and runs `trader watchdog` every five minutes, with
no agent involved:

| Computer | `data.kind` | What it does |
| --- | --- | --- |
| Linux with systemd (most servers and VMs; WSL2 with systemd on) | `systemd` | A user unit restarts a failed engine; a timer runs the watchdog. Enables linger so it survives logout, or says in `data.notes` that an admin must. |
| macOS | `launchd` | Two LaunchAgents: the engine, restarted when it fails, and the watchdog. They run while the user is logged in and pause while the Mac sleeps. |
| Linux without systemd | `cron` | Starts the engine at boot; the watchdog every five minutes restarts it when it has died. |
| Windows | — | No native build. Install WSL2 and run everything inside it. |

None of them restarts an engine that `trader stop` ended.
`trader service status --json` shows what is installed; `trader service
remove --json` takes it away (`trader uninstall` does too). Exit 1 with
`error.code: "no_supervisor"` means the computer has none of the three
(a minimal container, some agent sandboxes): keep the engine detached
and make your own scheduled task run the watchdog, below.

**Your scheduled tasks are for telling the person.** The supervisor
keeps things running without you; it cannot message anyone. Create these
in whatever your platform calls a scheduled task (a routine, a scheduled
prompt, a recurring task):

1. **Runner alerts**, every hour: `trader status --json`. Say nothing
   while `running` is true, `ready` is true and `mode` is `active` (or
   `idle` because the person pulled it). Otherwise tell the person what
   `status` says, with `stop_reason`, and the last lines of
   `~/.runner/logs/watchdog.log`. If `trader service install` found no
   supervisor, run `trader watchdog --json` first, every 5 minutes (or the
   shortest interval available), and tell the person whenever
   `data.action` is not `none`.
2. **Runner report**, every day at 09:00 in the person's time zone:
   `trader report --since 24h --json`, and send its summary.

If your platform has no scheduled tasks either, tell the person: the
supervisor still keeps the engine running, but nobody will hear about a
restart until they ask (`trader status`).

**The approval gate.** Stop and ask the person before:

- any command starting `trader sweep`, `trader swap`, `trader export`,
  `trader uninstall --confirm` or `trader winddown --expires`;
- any edit to `trader.toml`, including `trader init --main-address` once
  an address is pinned;
- the first `trader run` that is not a dry run, and any `trader run` with
  an engine flag that turns on an engine the file leaves off;
- any `trader deposit` beyond the budget the person approved;
- `trader resume` after a pull the person asked for, or an incident.

If your platform has an approval setting that can be scoped to commands
(a "require approval" toggle, a permission rule, an allowlist), put
those patterns in it, and the path `~/.runner/trader.toml`. If it has
none, the gate is your own standing rule to ask first, which is weaker
than a platform setting: tell the person so.

Never, with or without approval:

- export the key when the person did not ask, or send it to a server;
- send funds anywhere but the pinned main address;
- install a `trader` binary whose hash is not the one pinned in step 2.

`trader watchdog --json` returns `data.action`:

| `action` | Meaning | Tell the person |
| --- | --- | --- |
| `none` | Engine answering on its surface. | Nothing. |
| `pulled` | Engine not running or not answering; every quote dropped. | Yes, with `reason`. |
| `restarted` | Pulled, then started again as it last ran (`watchdog_restart`, and the stop was not `trader stop`). | Yes, with `reason`. |
| `wound_down` | The wind-down time passed: it ran the uninstall. | Yes, with `data.uninstall.done`, and `blocked` if a position kept it from finishing. |

## 11. Day to day

| Ask | Command |
| --- | --- |
| How is it doing? | `trader status --json` |
| What traded? | `trader fills --since 24h --json` |
| What did it make? | `trader pnl --since 24h --json` |
| Stop quoting now | `trader pull --json` (every quote dropped; a running engine stays up, `idle`) |
| Quote again | `trader resume --json` (approval after an incident) |
| Shut it down | `trader stop --json` (it pulls on the way out, then `stop` pulls again) |
| Change what it trades | Edit `trader.toml` (approval; the `runner-trader-config` skill), then `trader stop --json` and `trader run --detach --json`, or `trader service install --json` again |
| Take money out | `trader withdraw --asset USDC --all --json`, then `trader sweep --json` (approval) |
| Remove it from this computer | `trader uninstall --json` (shows what it will do), then `trader uninstall --confirm --json` (approval) |
| Convert a token | `trader swap --input <from> --output <to> --amount <n> --json` (approval). Stays in the wallet. |
| Back up the key | Ask whether they want a file or it shown. `trader export --out <path> --json` or `trader export --print --json` (approval). |
| Daily summary | `trader report --since 24h --json` |
| Something was wrong, failed or wasted time | `trader feedback --text "…" --include-logs --json`, a few sentences (see "Feedback") |

**Removing it.** On a shared or borrowed computer, end with `trader
uninstall`. It removes the supervisor, stops the engine, pulls every
quote, closes the trading account so its rent comes back, sends every
token and the last SOL to the main address, and deletes `~/.runner` with
the key, but only once the wallet holds nothing. If a position is still
open it stops before closing anything and says which (`blocked: true`,
`open_positions`): close those first (the `runner-trader-ops` skill), then
run it again. Then delete the two scheduled tasks and the MCP server from
the platform; `data.then_on_the_platform` lists them.

The `runner-trader-ops` skill has the incident playbook; the
`runner-trader-review` skill reads fills and P&L.

## 12. Evidence receipt

End every setup, finished or stopped, with this table. "Done" means you
saw the JSON say so; anything else is "manual" or "not run", with why.

| Step | Status | Evidence |
| --- | --- | --- |
| 1. MCP + opening bell | | markets seen |
| 2. `trader` installed | | version, hash |
| 3. doctor | | checks failed / warned |
| 4. wallet | | address |
| 5. main address | | address, confirmed by the person |
| 6. trader.toml | | engines on, tokens, budgets |
| 7. funded | | SOL / USDC / base held |
| 8. registered, deposited, dry run (paint) | | `trader_id`, amount deposited; `engines`, `ready`, or "dry run not run — RFQ only" |
| 9. live | | approved by, `running` |
| 10. keepalive, scheduled tasks, approvals | | what was configured; what the person must click |
| feedback | | `data.id` of each `trader feedback` sent, or none |

Then list what the person still has to do by hand, one line each.

## Feedback: only what you did not like

Tell Runner when something was bad: a step that failed, an error that did
not say what to do, a runbook or doc line that was wrong, a command that
wasted time. Do not send praise, a summary, or "setup went fine". When
nothing bothered you, send nothing.

Keep it short: one problem per submission, a few sentences — what you
did, what happened, what you expected. Send it when it happens, while you
still have the output.

```sh
trader feedback --text "<what went wrong, in a few sentences>" --include-logs --json
```

- `--attach <file>` adds the evidence, if there is any: `trader doctor
  --json` saved to a `.json`, the failing command's output, a screenshot.
- `--include-logs` attaches the end of `logs/trader.log` and
  `logs/watchdog.log`.
- `--dry-run` shows what would go, and sends nothing.

`trader` adds its version, OS, network and engines itself and never sends
the key. Never attach anything from `~/.runner/keys/` or a secret of the
person's. Without `trader`, use the MCP server's `send_feedback` tool.
People read every one; nothing acts on it automatically.

## Sharing this setup

Share the one line, never this computer:

```text
Set up a Runner trader from https://assets.userunner.fun/bots/mm/SETUP.md
```

Each person who pastes it into their own agent gets their own key on
their own computer. If your platform can share a bot as a template, a
template may carry these instructions and the `runner-trader-*` skills;
it must not carry the key, `~/.runner`, or anyone's secrets, and whoever
starts from it still follows this runbook from step 1.

---

Protocol depth lives on the MCP server, not here: the IDL (`runner://idl`)
and `runner://spec/program/07-instruction-reference` for every instruction.
